14 septiembre 2011

Herencia en JavaScript

JavaScript es en esencia un lenguaje basado en prototipos, que sufre de personalidades múltiples: tiene algunos operadores y comportamientos de un lenguaje con clasificación.

Para entender esto olvidemos por un momento a JavaScript.

Un lenguaje orientado a prototipos -como por ejemplo Self- se basa en que uno obtiene nuevas instancias a partir de la clonación de otras, por ejemplo en un "pseudo-JavaScript":

var auto = {
     acelerarACienEnUnSegundo: function() { return 'No puedo'; },
     arrancar: function() { return 'brummm... :)' }
};

var ferrari = clonar(auto);
ferrari.acelerarACienEnUnSegundo = function() { return 'fiuuummm'; };
ferrari.arrancar(); // retorna brummm....

¿Donde esta la esquizofrenia de JavaScript? Esta en que contamos con un operador new que simula comportarse como si estuviésemos trabajando con un lenguaje con clasificación, por ejemplo:

var auto = new Auto();
function Auto() {
     this.arrancar = function() { return 'brummm....'; };
}

¿Qué hace new?
Cada función que uno define en JavaScript es un objeto que tiene asociada una propiedad prototype.
Es como si cuando uno escribe function Auto() {} escribiera:

var Auto = {
    prototype: {}, 
    constructor: function() { /* cuerpo de la función Auto */ } };

Cuando se usa el operador new JavaScript lo que hace es:

  • Clonar el objeto prototype
  • Después evaluar la función en constructor usando la nueva instancia como contexto (es decir this es el clon de prototype)

Es por eso que uno podría escribir la función Auto como:

function Auto() { console.debug(this.arrancar); }
Auto.prototype.arrancarfunction() { return 'brummm....'; }; 

Esto es muy confuso, por que pareciera que una función se puede comportar como si fuese una clase. De hecho genera más confusión aún cuando uno quiere compartir implementación usando algo similar a la herencia.

Sin embargo el problema es más simple de resolver: no hay clases ni tipos, solo tenemos que agregar los métodos que necesitamos compartir, por ejemplo:

var agregarComportamientoDeAuto = function(obj) {
      obj.arrancar =  function() { return 'brummm... :)' };
}

¿Como creamos un objeto que se comporte como un auto?

var ferrari = function() {
      var nuevaInstancia = {};
      agregarComportamientoDeAuto(nuevaInstancia);
      nuevaInstancia.acelerarACienEnUnSegundo = function() { return 'fiuuummm'; };
};

Quizás no luzca muy elegante, pero en mi opinión es mejor que simular un lenguaje con clasificación donde no lo hay.
La ventaja de este método es que es muy fácil crear mixins o tener variables privadas (en el ejemplo podríamos pasar argumentos adicionales a agregarComportamientoDeAuto y aprovechar las clausuras).

También es posible simular delegación estilo "super", la próxima voy a dar un par de ejemplos de eso, pero adelanto que es una solución muy poco elegante.

No hay comentarios.:

Publicar un comentario