jueves, 14 de febrero de 2013

Herencia en javascript

Siempre he dicho que no me gustan ni coffescript, ni typescrpit porque si lo que necesitas en programar en javascript lo mejor que puedes hacer comprender este lenguaje y usarlo.
Por caprichos del destino, hace unos días me encontré programando componentes complejos en javascript y cuando necesitaba herencia me iba a la pagina de typescript y miraba cómo compilaba en js. Al aumentar la complejidad de las clases menos comprendía el código generado, en ese momento me di cuenta que estaba contradiciendo.
Este post son las conclusiones que he sacado al investigar como funciona la herencia en javascript. Espero que le resulte útil a más gente ya que en este contexto la herencia no se basa en clases (como en c# o java) y supone un cambio de mentalidad.
Tipos en javascript
Es importante recordad que javascript es un lenguaje tipado (String, Number, Boolean, Array, Object, Null, Undefined), pero a su vez es dinámico, es decir que una variable puede cambiar de tipo en cualquier momento, por ejemplo:
var x; //Undefined
x = 152; //Number
x = 'Manel'; //String
Creando objetos

En javascript podemos crear objetos de muchas formas en este post vamos a ver un par de ellas. Una de las más utilizadas es mediante el uso de JSON (JavaScript Object Notation)
var x = { name: "manel", job: "developer" };
Examinándolo con Chrome vemos que es un objeto:
image

Otra forma de crear objetos es utilizando un constructor. Cualquier función en javascript invocada con new se convierte en un constructor que devuelve un objeto. No es obligatorio que en el cuerpo de la función se devuelva un objeto (es más los expertos en javascript recomiendan que no escriba return en los constructores). Por ejemplo, para crear un objeto igual que el anterior escribiríamos algo como esto:
function Manel() {
 this.name = "manel";
 this.job = "developer";
}
var y = new Manel();
image
Podemos mejorar el constructor pasándole parámetros para las propiedades
function Person(name, job) {
 this.name = name;
 this.job = job;
}
var y = new Person("manel", "developer");

Prototype
Javascript no tiene clases, pero desde la versión 1.1 dispone de prototype, un objeto que sirve a modo de plano en el que se define la estructura del objeto.
function Person(name, job) {
 this.name = name;
 this.job = job;
 this.say = function (msg) { return this.name + " says " + msg; }
}
var y = new Person("manel", ".net developer");
var z = new Person("pedro", "java developer");

image

Como se puede ver en la exploración de objetos de Chrome, z e y son dos objetos que tiene como prototipo Person. Hay que recordar que no hay clases en javascript y que salvo los tipos String, Number, Boolean, Array, Null, Undefined el resto de elementos son objetos, incluidas las funciones. Si a esto le sumamos que podemos reasignar cualquier valor en tiempo de ejecución, veremos que en javascript la sobreescritura de métodos se realiza a nivel de objeto y que es un simple cambio de valor de una propiedad.

Siguiendo con el ejemplo anterior, después de crear los dos objetos podemos remplazar en uno de ellos el método say y no pasa nada, es más los dos siguen teniendo el mismo prototype:
function Person(name, job) {
 this.name = name;
 this.job = job;
 this.say = function (msg) { return this.name + " says " + msg; }
}
var y = new Person("manel", ".net developer");
var z = new Person("pedro", "java developer");

y.say = function (msg) { return msg };

image

Propiedades personalizables al constructor, métodos comunes en el prototipo.
Las funciones son objetos, en los ejemplos anteriores estamos creando para cada Persona una función say. Para evitar la creación de una función distinta (aunque tengan el mismo cuerpo) debemos de crear la función en el prototipo, tal y como se indica a continuación
function Person(name, job) {
 this.name = name;
 this.job = job;    
}
Person.prototype.say = function (msg) { return this.name + " says " + msg; }

var y = new Person("manel", ".net developer");
var z = new Person("pedro", "java developer");

image

Cuando invocamos a la función Person como un constructor (utilizando la palabra new) se genera un nuevo objeto que tiene de prototype Person. Es el prototype el que contiene la función say, así que aunque creamos mil objetos con el constructor Person, únicamente habrá una instancia de say ya que todos los objetos comparten el mismo prototipo.

Jerarquía en la herencia
Cuando llamamos a la propiedad, javascript primero evalúa si esa propiedad existe en el objeto, si no existe lo busca en las propiedades de su prototype. Si no encuentra ninguna propiedad devolverá undefined.
En el ejemplo anterior, los objetos z e y no tienen la función say, quien lo tiene es el prototipo, así que solo habrá un único objeto función que será utilizada por todos los objetos Person.

Cómo ejecutar funciones de prototipos padre.
En determinadas circustancias, cuando sobreescribimos funciones de un objeto, nos puede interesar ejecutar el mismo método, pero del prototipo base. Para hacer esto tenemos que tener una referencia del objeto funcion e invocarlo con call pasandole como primer parametro this seguido, si tiene, por el resto de parámetros. Veamos un ejemplo:
function Person(nombre) {
 this.name=nombre;
}
Person.prototype.sayHello = function () {
 return this.name + " says hello";
}
        
function Developer(name, language) {
 Person.call(this, name);
 this.language = language;
}
Developer.prototype = new Person();
        
Developer.prototype.sayHello = function () {
 return Person.prototype.sayHello.call(this)
 + " and he can write in " + this.language;
};
        
var x = new Developer("pedro", "java");
var y = new Developer("manel", "c#");

Aquí podemos como creamos una jerarquía de prototipos. El prototipo Person tiene una función sayHello. A Developer se le asigna como prototype una nueva instancia de Person a la que le cambiamos el método sayHello. Por si no ha quedado la explicación clara, muestro la exploración del Chromeimage

Si has llegado hasta aquí, muchas gracias por leer este ladrillo y espero que haya servido para algo.

No hay comentarios:

Publicar un comentario