prototipos-en-javascript

Prototipos en JavaScript

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 y edad 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 de Persona, por lo que todas las instancias de Persona 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

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étodo saludar.
  • Perro se crea utilizando Object.create(Animal), lo que establece a Animal como su prototipo.
  • Perro tiene propiedades propias (tipo = "perro") y métodos adicionales (ladrar).
  • Como Perro hereda de Animal, puede usar el método saludar definido en Animal.

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.