En JavaScript, cada objeto posee una referencia interna denominada [[Prototype]], comúnmente acccesible mediante la propiedad __proto__. Esta referencia apunta a otro objeto, denominado su prototipo.
Las funciones constructoras cuentan con una propiedad prototype que apunta a un objeto. Este objeto prototipo contiene, a su vez, una propiedad constructor que apunta de vuelta a la función constructora original.
function Usuario(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
Usuario.prototype.saludar = function() {
console.log(`Hola, soy ${this.nombre}.`);
};
const usuario1 = new Usuario('Ana', 28);
const usuario2 = new Usuario('Luis', 32);
usuario1.saludar(); // "Hola, soy Ana."
usuario2.saludar(); // "Hola, soy Luis."
// Verificaciones de igualdad prototipal
console.log(usuario1.__proto__ === Usuario.prototype); // true
console.log(usuario2.__proto__ === Usuario.prototype); // true
console.log(usuario1.__proto__ === usuario2.__proto__); // true
console.log(Usuario.prototype.constructor === Usuario); // true
Nota importante:
- El uso de
__proto__es considerado herencia. La recomendación moderna es emplear los métodosObject.getPrototypeOf()yObject.setPrototypeOf(). - Un objeto creado con
Object.create(null)carece de enlace a un prototipo (__proto__esnull).
Cadena de prototipos
Cuando se accede a una propiedad o método en un objeto, el motor de JavaScript no solo busca en el propio objeto. Si no lo encuentra allí, continúa la búsqueda en el objeto apuntado por su __proto__ (su prototipo), y luego en el prototipo de ese prototipo, fromando una cadena. Este proceso se repite hasta encontrar la propiedad o alcanzar el final de la cadena, que es null.
const conjunto = [5, 10, 15];
console.log(conjunto.valueOf()); // [5, 10, 15]
Para ejecutar valueOf() en el arreglo, el motor sigue esta secuencia de búsqueda:
- Revisa el propio objeto
conjunto. No encuentravalueOf. - Busca en
conjunto.__proto__, que esArray.prototype. Tampoco lo encuentra. - Continúa en
Array.prototype.__proto__, que esObject.prototype. Aquí sí encuentra el métodovalueOfy lo ejecuta.
console.log(conjunto.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(conjunto.__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true (fin de la cadena)
La cadena de prototipos resultante es: conjunto -> Array.prototype -> Object.prototype -> null.
Mecanismos de herencia en JavaScript
Herencia de propiedades
Para heredar propiedades de un constructor padre, se invoca dicho constructor dentro del constructor hijo, utilizando call() o apply() para asignar el contexto this.
function Animal(tipo, nombre) {
this.tipo = tipo;
this.nombre = nombre;
}
function Mascota(tipo, nombre, dueno) {
// Hereda las propiedades 'tipo' y 'nombre' de Animal
Animal.call(this, tipo, nombre);
this.dueno = dueno;
}
const miMascota = new Mascota('Perro', 'Rex', 'María');
console.log(miMascota.tipo); // "Perro"
console.log(miMascota.nombre); // "Rex"
console.log(miMascota.dueno); // "María"
Herencia de métodos (Prototipos)
Para que los objetos creados por un constructor hijo hereden los métodos definidos en el prototipo del padre, se debe establecer una relación prototipal correcta. La técnica común implica crear un objeto cuyo prototipo sea el prototipo del padre y asignarlo como prototipo del hijo.
// Método en el prototipo del padre
Animal.prototype.mostrarInfo = function() {
console.log(`${this.nombre} es un ${this.tipo}.`);
};
// Establecer la cadena prototipal para la herencia de métodos
Mascota.prototype = Object.create(Animal.prototype);
// Restablecer la propiedad constructor, ya que fue sobrescrita
Mascota.prototype.constructor = Mascota;
// Añadir un método específico al hijo
Mascota.prototype.presentarDueno = function() {
console.log(`${this.nombre} pertenece a ${this.dueno}.`);
};
const otraMascota = new Mascota('Gato', 'Luna', 'Carlos');
otraMascota.mostrarInfo(); // "Luna es un Gato."
otraMascota.presentarDueno(); // "Luna pertenece a Carlos."
console.log(otraMascota instanceof Mascota); // true
console.log(otraMascota instanceof Animal); // true
Comprobación de propiedades propias
Debido a la búsqueda a lo largo de la cadena de prototipos, puede ser ineficiente verificar si una propiedad pertenece directamente a un objeto. El método hasOwnProperty() es la forma segura de realizar esta comprobación sin atravesar la cadena.
console.log(otraMascota.hasOwnProperty('nombre')); // true (es propia)
console.log(otraMascota.hasOwnProperty('mostrarInfo')); // false (está en el prototipo)