marzo 28, 2024

BitCuco

¡Hola Mundo!

Herencia de clases en JavaScript y su funcionamiento

herencia javascript

Muchos desarrolladores que provienen de un modelo de herencia clásico de la programación orientada a objetos, y encuentran difícil comprender la herencia de prototipos en JavaScript. La herencia de clases en Javascript permite reciclar métodos y atributos de una clase padre y usarlos en la clase hijo de forma natural. Para explicarlo de manera más sencilla lo mostraremos con ejemplos.

Considere el siguiente código donde creamos una instancia de clase usando un código que es parte de una herencia normal en Javascript. Como en toda clase, generamos un prototipo, que en éste ejemplo contiene unas variables predefinidas: primitiva, un objeto y un array, las cuáles son el prototipo que se crea al definir el objeto que tiene herencia en Javascript. Además crearemos dos instancias de la clase (cada instancia es independiente)

function Class () {
    this.obj2 = {c: 'c'}
}

Class.prototype = {
    primitive: 'a',
    obj: {a: 'a'},
    arr: [1,2,3]
};

var instancia1 = new Class ();
var instancia2 = new Class ();

Cuando se crea una nueva instancia, la instancia al objeto obtiene los valores como se declaran en la clase. A ésto se le llama herencia en Javascript. El siguiente diagrama nos proporciona una noción más clara de la asignación, en el prototipo se asignan al definirse el objeto (flechas) y en las instancias se obtienen los valores declarados en el nuevo objeto (líneas punteadas).

herencia javascript

Una forma equivalente de crear una instancia, en cierta manera más formal, es la siguiente:

// Crea un objeto vacío encadenándolo a Class.prototype

var instancia1 = Object.create (Class.prototype);

// Inicializa el objeto usando el constructor

Class.call (inst1);

Lo importante a entender es que el objeto prototipo es compartido por todas las instancias, ya que es una propiedad que tienen las instancias de la herencia en Javascript. Un error común es crear una instancia de un objeto / matriz en el prototipo sin darse cuenta de que mutar ese objeto lo modificará en todas las instancias. En nuestro ejemplo, el objeto {a: ‘a’} y la matriz [1,2,3] se comparten entre las instancias, mientras que cada instancia tiene su propia copia de {c: ‘c’}

// Las dos instancias comparten la misma instancia de obj
// que puede ser inesperado

instancia1.obj.a = 'b';
console.log (instancia1.obj); // {a: 'b'}
console.log (instancia2.obj); // {a: 'b'}

// lo mismo para las matrices (ya que la matriz es un objeto mutable)

instancia1.arr.push (4);
console.log (instancia1.arr); // [1,2,3,4]
console.log (instancia2.arr); // [1,2,3,4]

// para evitar esto, cree los objetos secundarios en el constructor
// ahora cambiar obj2 en instancia1 no afecta instancia2

instancia1.obj2.c = 'd';
console.log (instancia1.obj2); // {discos compactos'}
console.log (instancia2.obj2); // {c: 'c'}

Así los cambios en cada instancia se realizan a nivel constructor, nunca a nivel prototipo para evitar que se modifiquen los valores para todas las instancias de la clase.

También hay que tener en cuenta que este problema no se produce al compartir primitivas en el prototipo. Cuando leemos una propiedad de un objeto, primero buscará la propiedad en el objeto especificado, si no la encuentra, subirá a la cadena del prototipo buscando ese objeto. Sin embargo, cuando escribe en un objeto, siempre escribe en el objeto especificado, no en el prototipo.

// Aquí estamos creando una nueva propiedad en instancia1, que ahora sombrea la propiedad "primitiva" en "Class.prototype"

instancia1.primitive = 'b';

// Todavía al leer de Class.prototype

console.log (instancia2.primitive); // 'a'

// Al leer "obj" del prototipo y luego escribe sobre "a" propiedad en ese "obj" (del prototipo)

instancia1.obj.c = 5;

// Y al crear un nuevo objeto que escribe una nueva propiedad "obj" en la instancia2 directamente, conserva el "obj" definido en el prototipo

instancia2.obj = {c: 6};

Breves comentarios sobre la herencia en Javascript

  • La inicialización de objetos inmutables en un prototipo mantiene una sola copia, por lo que ahorra memoria y código de inicialización.
  • No comparta objetos mutables en el prototipo a menos que desee que los cambios en una instancia se reflejen en todas las instancias.
  • Inicializar primitivas en los prototipos siempre está bien, porque no se pueden mutar directamente a través de instancias.
  • Para conservar valores diferentes en cada una de las instancias, éstas se deben acceder por medio del constructor y nunca por el prototipo.

Ver más en: JavaScript Bits.

Conoce también: Tipos de Datos en Java y Kotlin