Esto de los prototypes son algo para crear métodos propios para los objetos. Es parte de lo que se llama programación orientada a objetos. El escenario, creo, es algo así como cuando se tiene un montón de objetos con muchos datos, es decir, llaves y valores. Entonces también hay conjuntos de objetos con llaves valores semejantes pero no iguales.
Entonces con los datos de estos objetos se tienen que sacar cálculos complejos, para ello podríamos usar código para hacer funciones que hagan estos cálculos. Pero pasaría que tendríamos un montón de funciones para estos cálculos y que además muchas de estas funciones serían muy parecidas pero no iguales debido a que, como mencioné, habrán conjuntos de objetos semejantes pero no iguales.
Para scripts complejos y con mucho código, tales como en sistemas bancarios, estadísticas o qué sé yo, y que a su vez son usados por muchos programadores a la vez, evidentemente habría bastante rollo al determinar cuál función es (de entre tantas y con nombres tan parecidos) para qué conjunto de objetos.
Entonces estos prototipos se usan para crear funciones que luego no hará falta llamarlas como se hace típicamente, si no que se usará como si fuesen métodos, de hecho lo son, para ese objeto en particular. La lista de esos prototypes se pueden ver en la consola. Solo expande la lista de prototypes y se tiene a la mano los nombres de los métodos asociados a ese objeto en particular.
Cuando vemos un objeto en la consola siempre vamos a ver que tiene un “proto” relacionado. Creo que ya eso no se usa, los “proto”. Pero de la misma manera que aparecen los proto, también empezarán a aparecer los prototypes cuando empecemos a crearlos.
Crear un prototype
Para crear un prototype tenemos que empezar creando un object constructor. No se puede asignar prototipos a un objeto creado de forma literal.
//Creación de un object constructor
function Pelicula(datoNombre, datoYear) {
this.nombre = datoNombre;
this.year = datoYear;
}
Ahora, se crea un objeto (instancia) a partir del constructor de objetos creado arriba y aplicándole un console.log:
const elPadrino = new Pelicula('El Padrino', 1975);
console.log(elPadrino);

Podemos, al desplegar los prototypes, ver una lista, donde, en este caso, tenemos listada la función constructora, pero todavía no hemos creado un prototype.
Ahora creamos el prototype, no se puede usar función de flecha.
Pelicula.prototype.presentacion = function () {
console.log(`La película ${this.nombre} fue creada el año ${this.year});
}
En este caso he creado un prototipo de nombre presentacion que será exclusivo de objetos que hayan sido creados con el object constructor Película.
Ya creado el prototype aplico de nuevo un console.log y llamo a la función como si fuese un método.
console.log(elPadrino);
elPadrino.presentacion();

Ahora podemos ver que en el listado de prototypes tenemos el “método” presentacion que es exclusivo para ese objeto que fue construido con el object constructor Pelicula.
Y vemos cómo se ejecuta el código contenido en la función que se creó para el prototipo solo con la sintaxis del punto. Esto puede dar cierto orden a grandes cantidades de código.
Referencia de prototypes dentro de otro prototypes
Podría hacer uso del algún prototype mientras estoy creando uno nuevo.
function Cliente(nombre, saldo) {
this.nombre = nombre;
this.saldo = saldo;
}
// Prototype para tipo de cliente
Cliente.prototype.tipoCliente = function() {
let tipo;
if(this.saldo > 10000) {
tipo = 'Gold';
} else if(this.saldo > 5000) {
tipo = 'Platinum';
} else {
tipo = 'Normal';
}
return tipo;
}
// Otro Prototipo para el nombre completo que incluye el dato cliente.tipoCliente que a su vez se obtiene con el
//prototype anterior
Cliente.prototype.nombreClienteSaldo = function() {
return `Nombre: ${this.nombre}, Saldo ${this.saldo}, Tipo Cliente: ${this.tipoCliente()} `;
}
//Un tercer prototype para modificar un dato dentro del objeto donde a la función se le establece un parámetro (retiro)
Cliente.prototype.retiraSaldo = function(retiro) {
this.saldo -= retiro;
}
// Instanciando, creando un objeto con el object constructor creado arriba
const pedro = new Cliente('Pedro', 6000);
// Usando el primer prototipo creado para determinar el tipo de cliente
console.log ( pedro.tipoCliente() );
// El segundo prototipo que a su vez usa el primer prototipo
console.log ( pedro.nombreClienteSaldo() );
//El tercer prototipo que modifica un valor del objeto
pedro.retiraSaldo(2000);
//De nuevo el segundo prototipo pero ahora se verá el valor que ha sido modificado con el tercer prototipo
console.log ( pedro.nombreClienteSaldo());
//Un vistazo al objeto para ver su lista de prototipos
console.log(pedro);

Arriba podemos ver los resultados de los console.log. Se puede ver cómo en el segundo resultado aparece el tipo de cliente que a su vez es un dato que se saca con otro prototype. También podemos ver la lista de prototipos para el objeto pedro. Esa lista de prototype está vinculada al Object Constructos Cliente.
Herencia de constructores y prototypes
Una de las cosas que quiero que ocurra en estos artículos es que lo que plasme aquí es porque quedó entendido. Parte de eso no es solo saber cómo se hace algo, si no también por qué se hace. Eso me pasa aquí con el tema de la herencia de los prototipos. Que queda claro el tema de bueno, que analizando un poco le podría de alguna manera encontrar la utilidad a heredar los valores de otros objetos construidos con otros objects constructors y sus prototypes. Pero no le encentro el por qué al hecho de heredar también el constructor de esos objetos si el objeto que está heredando tiene su propio constructor.
El método .call(this, dato1, dato2…) para heredar datos de un objeto constructor a otro
Este método, por los momentos, se usa con las funciones objects constructores. Cuando estoy creando un object constructor yo puedo pasarle datos de otros objects constructors.
function Persona(nombre, saldo, telefono) { // Crear 2 objetos nuevos...
// this.nombre = nombre;
// this.saldo = saldo;
// this.telefono = telefono;
// Debe ser:
Cliente.call(this, nombre, saldo);
this.telefono = telefono;
}
En el ejemplo anterior se está creando una función constructora que va a trabajar creando objetos, obvio. Pero estos datos son los mismos con los que trabaja otros objetos que se crearon a partir de otra función constructora. Entonces no necesito escribir de nuevo todos estos datos this.dato.
Para ello hago como una especie de copia con NomFunOC.call(this, dato1, dato2, dato3…). Estaría resumiendo en una sola línea tantas líneas de código como datos tenga la función constructora que estoy copiando, que, por qué no, podrían ser miles.
Esta es la lógica que le veo a ello, resumir líneas de código. Porque en un principio creía que también se traía los datos de los objetos creados con la función constructora que se copió para ser usados con los nuevos prototypes que se crearán en la nueva función constructora. Pero veo que no es así. No obstante, con esto tengo un cómo se hace y un por qué.
El método Object.create( Cliente.prototype )
El método anterior es para copiar los datos dentro de la nueva función constructora desde otra función constructora previa. Este método es para copiar también los prototipos de esa función constructora, la sintaxis es la siguiente:
Persona.prototype = Object.create( Cliente.prototype );
Algo así como: en los prototypes de mi nueva función constructora Persona, créame un objeto nuevo que va a ser los prototypes de la función constructora Cliente. Según el instructor, este método “Object.create”, fue creado (valga la redundancia) para precisamente ser usado para esto.
Con esto puedo usar los métodos propios de los prototipos de la función original y en los objetos creados con la nueva función constructora. Suena un poquito a parafernalia. Pero le veo el sentido si estamos usando, en parte, datos similares. Para qué volver a crear estos prototipos si son los mismos y me sirven.
Heredar el constructor (¿o reemplazar?)
Aquí está mi confusión en esto de los prototype. El instructor dice que se trata de heredar el constructor. Y yo lo que veo es que se está reemplazando el constructor del objeto Persona por el constructor del objeto Cliente. La sintaxis es la siguiente:
Persona.prototype.constructor = Cliente;
Lo que yo veo arriba es, siguiendo la lógica de acceso por sintaxis de puntos, es que estoy diciendo: oye, el constructor que está dentro de los prototipos de objeto Persona va ahora a ser igual a Cliente, ya no ha Persona. Por lo que a mi me parece que no se está heredando si no más bien reemplazando.
Un vistazo a todo
function Cliente(nombre, saldo) {
this.nombre = nombre;
this.saldo = saldo;
}
// Obtener Tipo de Cliente
// Con prototypes tienes que utilizar function, function buscará en el mismo objeto mientras que un
Cliente.prototype.tipoCliente = function() { // arrow function irá hacia la ventana global, marcándote un undefined
let tipo;
if(this.saldo > 10000) {
tipo = 'Gold';
} else if(this.saldo > 5000) {
tipo = 'Platinum';
} else {
tipo = 'Normal';
}
return tipo;
}
// Otro Prototipo para el nombre completo
Cliente.prototype.nombreClienteSaldo = function() {
return `Nombre: ${this.nombre}, Saldo ${this.saldo}, Tipo Cliente: ${this.tipoCliente()} `;
}
Cliente.prototype.retiraSaldo = function(retiro) { //Un tercer prototype que modifica uno de los valores del objeto Cliente
this.saldo -= retiro;
}
const pedro = new Cliente('Pedro', 6000); // Instanciarlo
console.log(pedro);
console.log ( pedro.tipoCliente() ); // Acceder a los prototypes
console.log ( pedro.nombreClienteSaldo() ); // Un prototype que accede a otros prototypes
pedro.retiraSaldo(2000); // reescribir un valor
console.log ( pedro.nombreClienteSaldo()); // comprobar saldo
// NUEVO: Heredar Prototypes
function Persona(nombre, saldo, telefono) { // Crear 2 objetos nuevos...
// this.nombre = nombre;
// this.saldo = saldo;
// this.telefono = telefono;
// Debe ser:
Cliente.call(this, nombre, saldo);
this.telefono = telefono;
}
Persona.prototype = Object.create( Cliente.prototype ); // Heredar la función ( Antes de Instanciarlo )
Persona.prototype.constructor = Cliente; // Heredar el constructor
Persona.prototype.mostrarTelefono = function() { // Crear Prototype solo para Persona...
return `El teléfono de este cliente es: ${this.telefono}`
}
const juan = new Persona('Juan', 6000, 1120192); // Instanciarlo
console.log(juan);
console.log ( juan.nombreClienteSaldo() );
console.log ( juan.mostrarTelefono() );

Un análisis paso a paso

En el caso de arriba están creados dos instancias de dos constructores independientes.

En este caso he heredado algunos datos del constructor Cliente al constructor Persona. Ahora el objeto creado con el constructor persona puede desplegar los datos nombre y saldo que antes no podía.

En este caso he heredado los prototipos del constructor Cliente. Con eso veo ahora una especie de sub prototype con los métodos del constructor cliente, incluso, incluyendo su propio constructor por que esto forma parte de los prototypes de un objeto. Pero ocurre que al haber hecho esto ahora ya no se observa el propio constructor del objeto Persona entro los prototipos principales.

En este caso invertí lo anterior, quité heredar los prototipos de Cliente pero heredé el constructor de Cliente. Ahora veo que se fue ese tipo de sub prototype pero aparece un constructor entre los prototipos de Persona pero ya no es el constructor Persona, si no el constructor Cliente.

Aquí lo puse todo, dejé la herencia del constructor y volví a heredar los prototipos del objeto Cliente. Y entonces se ve todo el paquete.
No obstante, aunque ya no se ve el constructor Persona, el objeto Juan sigue construyéndose con el constructor Persona. Eso sí, puedo aplicar los prototipos de Cliente en el objeto creado con el constructor Persona. Como es el caso del penúltimo console: “Nombre: Juan, Saldo 6000, Tipo Cliente: Platinum“.
Duda
Dado todo lo anterior no sé donde cuadra en todo esto la herencia del constructor. Por qué ya no me aparece el constructor anterior. Por qué al heredar los prototipos se me desaparece la línea del constructor propio del objeto.
Vienen algunos proyectos, imagino que allí veré como cuaja todo esto.