Herencia en Javascript

Una de las caracterísitcas asociadas al uso de clases y objetos es la herencia. Javascript la implementa mediante el objeto prototype incluído en cada constructor

La herencia significa que cuando se crea un objeto a partir de otro (el constructor), este objeto puede acceder a las propiedades y métodos de la clase que lo ha creado.

¿Como hace est Javascript? Con prototype. Hay quien dice que más que basado en objetos, Javascript es un lenguaje basado en prototipos.

El objeto prototype no es más que eso: un objeto básico de un constructor que contiene todos los métodos y propiedades que serán accesibles a los objetos basados en ese constructor.

¿Como funciona este objeto? Si desde un objeto se invoca un método o una propiedad el intérprete de javascript busca entre sus métodos o propiedades. Si no lo encuentra, busca en el prototype del objeto constructor. Si está se ejecuta. Si tampoco está repite el proceso: va al constructor de este objeto y busca en su prototype. Y así hasta encontrarlo o emitir un error si no existe en el último constructor. Eso es se suele conocer como la cadena prototype.

Veamos algo muy sencillo para ver la cadena

var Coches = function(m, c){

this.modelo = m;

this.color = c;

}

Coches.prototype.marca = "Airon";

Coches.prototype.fichar = function(){

    console.log(this.marca + " "+this.modelo)

}

micoche = new Coches("basic", "rojo");

sucoche = new Coches("sport", "azul");

console.log(micoche);

console.log(micoche.color);    //propiedad color de micoche

console.log(micoche.marca);    //propiedad marca leída en prototype

console.log(micoche.valueOf() );    //método de Object.prototype

Ves que el objeto micoche no tiene ni la propiedad marca ni el método fichar(), sin embargo micoche.marca o micoche.fichar() no da ningún error.

Como micoche no tiene ni la propiedad marca, el intérprete lo busca en el prototype de constructor, Coches. Ahí encuentra la propiedad marca, así que la leee y la muestra.

Igual ocurriría con fichar(), lo encuentra en Coches y lo usa con this apuntando a micoche, que es el objeto que posee al método en ese momento.

Y en la última linea se usa el método valueOf(). No es ni del objeto coche, tampoco se encuentra en el constructor Coches, pero si se encuentra en el constructor de este último Function. Se encuetnra y se ejecuta.

Es decir cada objeto constructor tiene una caja de herramientas que puede ser usada por sus objetos descendientes. Esa caja es el objeto prototype.

Esta caja puede llenarse con más herramientas. Así se pueden agregar propiedades y métodos a una clase ya definida.

Veamos la clase Rectangulo

class Rectangulo{

constructor(x,y){

    this.alto=x;

    this.ancho=y;

}

area(){

    return this.alto*this.ancho

    }

}

cuadro = new Rectangulo(10,20);

Rectangulo.prototype.perimetro= function(){return (this.alto+this.ancho)*2}

console.log("area "+cuadro.area() + "\nperimetro "+cuadro.perimetro());

En este ejemplo donde he usado class, el método area() es una propiedad de prototype del objeto rectangulo. Y le añado otro método, después de crear el objeto cuadro. Este nuevo objeto puede ser usado por cuadro como si fuera suyo.

Pero ahora vamos a por un ejemplo sencillo pero que usa las clases con un poquito más de nivel. Vamos a implementar un objeto que llamaremos figura con dos propiedades: alto y base. Parecido al rectángulo de antes, puedes hacerlo en una página de ejemplos

class Figura{

    constructor(x,y){

            this.alto=x;

            this.base=y;

        }

}

Como ves no tiene el método área. Ahora vamos a definir dos clases derivadas de esta para implementar un rectángulo y un triángulo con sus respectivos métodos área.

class Rectangulo extends Figura{

    area(){ return this.alto*this.base}

}

class Triangulo extends Figura{

    area(){ return (this.alto*this.base)/2}

}

Bien ahora tenemos dos clases para representar el rectángulo y el triángulo, y podemos calcular el area de cada uno usando la fórmula de cada figura geométrica.

var cuadro = new Rectangulo(4,5);

var triang = new Triangulo(4,5);

var areaRect = cuadro.area();

var areaTri = triang.area();

alert("areas: \nrect: "+areaRect+"\ntriang: "+areaTri )

Ambos objetos rect y triang han poseen las propiedades base y altura copiadas de la clase Figura, y además implementan un método para calcular el area, cada uno su propio método por que cada uno se crea con un constructor diferente.

Observa la cadena: el constructor de cuadro es Rectangulo, y el constructor de Rectangulo es Figura.

¿Y si ahora quiero añadirle una nueva propiedad a Figura? No se puede hacer directamente, para eso usamos los prototype. En esta caso sería

Figura.prototype.titulo = "Areas";

alert(rect.titulo);

Ahora rect y triang comparten la propiedad titulo, cuyo valor es "Areas".

Todos los objetos derivados de la clase figura acceden a la propiedad titulo. Han heredado esa propiedad y su valor. No poseen una copia, sino que comparten la propiedad desde el constructor.

Si se desea sobreescribe la propiedad heredada, lo que ocurre es que se crea una copia con el mismo nombre

rect.titulo = "Cuadro";

triang.titulo = "Porción";

alert(rect.titulo + " y " + triang.titulo);

Pero Javascript te permite agregar propiedades a cada objeto por separado. Por ejemplo podrias hacer

rect.color = "Rojo";

alert("Color de rect "+ rect.color);

Pero la propiedad color solo la tendría ese objeto rect, no el objeto triang. Y si defines otro Rectangulo tampoco tendría esa propiedad.

Ejemplo

Prototype es un objeto que se asigna a los objetos constructores.

 

El constructor es Coches. Le podemos añadir a su prototype la propiedad marca y el método ficha

Si creamos dos instancias de coche, ambas comparten el contenido de prototype.

El modelo en Coches no es accesible, los coches concretos sobreescriben esta propiedad y se le da el valor que deseemos.

La forma más cómoda para implementar esto en código javascript es mediante el identificador class

class Coches {

   constructor(m){

      this.modelo = m;

   }

   marca = "Fordcar"

   ficha(){

      console.log(this.marca + " "+this.modelo)

   }

}

miCoche = new Coches("basic");

suCoche = new Coches("sport");

console.log(miCoche.modelo); //basic

console.log(miCoche.marca); //Fordcar