JavaScript es un lenguaje orientado a objetos basado en prototipos, lo que significa que la herencia y la reutilización de código se gestionan a través de un sistema de prototipos.
El prototipo permite a los objetos heredar propiedades y métodos de otros objetos. Este mecanismo de herencia se denomina herencia prototípica.
Esto es permite que los objetos compartan métodos y propiedades, lo cual optimiza el uso de memoria.
Este es un enfoque diferente de otros lenguajes que emplean una estructura de clases, y es fruto de la naturaleza de tipado dinámico de JavaScript (y tiene sus ventajas y desventajas).
Además, es uno de los puntos que mas cuestan entender del lenguaje, así que vamos a verlo en profundidad 👇.
Qué es un prototipo
El prototipo es simplemente un objeto que todos los objetos tienen como propiedad interna, y al que pueden acceder.
Todos los objetos en JavaScript tienen la propiedad [[Prototype]]
, que a su vez es una referencia a otro objeto “padre”.
Esta secuencia de objetos conectados por prototipos se denomina cadena de prototipos.
Cuando intentas acceder a una propiedad o método que no existe en el objeto, JavaScript buscará si existe en el prototipo del objeto.
Seguirá buscando a través de la cadena de prototipos hasta que encuentre la propiedad o hasta llegar aun prototipo null
(que generalmente, significa que ha llegado a un objeto base)
Creación de objetos y prototipos
Vamos a ver en qué momento de la creación de un objeto se asocia el prototipo al objeto
Creación con literales de Objeto
Cuando creas un objeto usando una notación literal, JavaScript asigna automáticamente Object.prototype
como el prototipo del nuevo objeto:
const persona = {
nombre: "Carlos",
edad: 30
};
console.log(persona.__proto__ === Object.prototype); // true
Creación con Object.create()
También podemos crear un nuevo objeto con un prototipo específico usando Object.create(proto)
:
const animal = {
hacerSonido() {
console.log("Sonido");
}
};
const perro = Object.create(animal);
perro.hacerSonido(); // "Sonido"
En este caso, perro
tiene animal
como su prototipo, por lo que hereda el método hacerSonido
.
Función constructora y prototipo
Cuando usamos una función constructora con el operador new
, se crea un nuevo objeto que hereda del prototype
de esa función constructora.
function Persona(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
Persona.prototype.saludar = function() {
console.log(`¡Hola, mi nombre es ${this.nombre} y tengo ${this.edad} años!`);
};
// Creamos una nueva instancia de Persona
let persona1 = new Persona("Luis", 30);
// Llamamos al método `saludar` desde el prototipo
persona1.saludar(); // Salida: ¡Hola, mi nombre es Luis y tengo 30 años!
En este ejemplo:
- Persona es una función constructora que toma
nombre
yedad
como parámetros. - Persona.prototype es un objeto donde podemos agregar métodos y propiedades que serán compartidos por todas las instancias de
Persona
. - El método
saludar
se agrega al prototipo dePersona
, por lo que todas las instancias dePersona
pueden acceder a él.
Propiedad proto
Generalmente el prototipo de un objeto está disponible de forma pública a través de la propiedad __proto__
.
console.log(juan.__proto__ === Persona.prototype); // true
El acceso directo a la propiedad __proto__
no está recomendado. En lugar de __proto__
, es preferible utilizar los métodos estáticos proporcionados por el objeto Object
para trabajar con prototipos
Object.getPrototypeOf(obj)
Obtiene el prototipo del objeto.
console.log(Object.getPrototypeOf(juan) === Persona.prototype); // true
Object.setPrototypeOf(obj, proto)
Establece el prototipo del objeto.
const nuevoProto = { nuevaPropiedad: "valor" };
Object.setPrototypeOf(juan, nuevoProto);
console.log(juan.nuevaPropiedad); // "valor"
Modificación de prototipos
Podemos añadir métodos a un prototipo después de que se han creado instancias, lo que es útil para ampliar funcionalidades.
Persona.prototype.aniversario = function() {
this.edad++;
console.log(`Felicidades ${this.nombre}, ahora tienes ${this.edad} años.`);
};
juan.aniversario(); // Salida: "Felicidades Luis, ahora tienes 31 años."
Modificando el prototipo de objetos nativos
Una de las características poderosas de JavaScript es que podemos modificar los prototipos de objetos nativos, como Array
, String
, o Object
. Esto nos permite agregar métodos personalizados a los tipos básicos.
Array.prototype.imprimirPrimerElemento = function() {
console.log(this[0]);
};
let miArray = [10, 20, 30];
miArray.imprimirPrimerElemento(); // Muestra: 10
En este ejemplo, hemos agregado el método imprimirPrimerElemento
al prototipo de Array
. Ahora, todos los arrays en JavaScript tendrán este método disponible.
Aunque es posible modificar los prototipos de los objetos nativos, en general no es buena idea (de hecho suele ser bastante mala idea)
Modificar los prototipos de objetos nativos puede generar conflictos inesperados, especialmente en proyectos grandes o al trabajar con bibliotecas externas.
Herencia prototípica en JavaScript
La herencia prototípica es lo que permite que un objeto herede las propiedades y métodos de otro objeto. Esta característica es el núcleo del sistema de herencia de JavaScript.
Por ejemplo, consideremos el siguiente ejemplo, donde creamos dos funciones constructoras: Animal
y Perro
. La función Perro
va a heredar de Animal
mediante el prototipo:
// Creamos un objeto base: Animal
let Animal = {
saludar: function() {
console.log(`Hola, soy un ${this.tipo}`);
}
};
// Creamos un nuevo objeto que hereda de Animal
let Perro = Object.create(Animal);
// Definimos propiedades específicas para Perro
Perro.tipo = "perro";
Perro.ladrar = function() {
console.log("¡Guau!");
};
// Usamos el objeto Perro
Perro.saludar(); // "Hola, soy un perro"
Perro.ladrar(); // "¡Guau!"
En este ejemplo:
Animal
es un objeto base con un métodosaludar
.Perro
se crea utilizandoObject.create(Animal)
, lo que establece aAnimal
como su prototipo.Perro
tiene propiedades propias (tipo = "perro"
) y métodos adicionales (ladrar
).- Como
Perro
hereda deAnimal
, puede usar el métodosaludar
definido enAnimal
.
Sombreado de propiedades
Si una propiedad se define en un objeto y también en su prototipo, la propiedad del objeto “sombrea” la del prototipo.
Esto significa que al acceder a la propiedad, se obtiene el valor del objeto y no el del prototipo.