Concepto de herencia prototípica en Javascript

15 Abr 2011

Introducción

Javascript es un lenguaje orientado a objetos con altos niveles de abstracción.

En la definición del lenguaje, existen dos conjuntos básicos de elementos: aquellos que denominamos primitivas y los objetos. El primero, se compone de los valores undefined, null, booleanos, las cadenas y los números, mientras que el segundo, se corresponde con todo lo demás.

Las primitivas, por si mismas, no tienen propiedades: son únicamente valores. Sin embargo, aquellas de tipo booleano, cadena y número pueden ser asumidas por las instancias de sus respectivos objetos: Boolean, String y Number a través del comando new, por lo que, finalmente, son consideradas también como objetos.

El siguiente cuadro elaborado por Angus Crall resume perfectamente estas características del lenguaje:

typeof true; //"boolean"
typeof Boolean(true); //"boolean"
typeof new Boolean(true); //"object"
typeof (new Boolean(true)).valueOf(); //"boolean"
 
typeof "abc"; //"string"
typeof String("abc"); //"string"
typeof new String("abc"); //"object"
typeof (new String("abc")).valueOf(); //"string"
 
typeof 123; //"number"
typeof Number(123); //"number"
typeof new Number(123); //"object"
typeof (new Number(123)).valueOf(); //"number"

Objetos

En Javascript, los objetos son conjuntos de propiedades. Una propiedad puede referir a otro objeto o a una primitiva.

Además de propiedades, un objeto contiene un objeto prototípico único. Este protopipo, que define la naturaleza del lenguaje Javascript, puede ser a su vez tanto un objeto como, un valor null.

Cuando un prototipo corresponde con un objeto, éste posee a su vez su propio prototipo con, de nuevo, la posibilidad de corresponderse con un nuevo objeto o un null. Esto permite crear por anidación, lo que conocemos en Javascript como la cadena prototípica.

Una cadena prototípica se corresponde finalmente con un conjunto de objetos unidos por referencias que permiten la implementación del concepto de herencia y propiedades compartidas.

Tomemos por ejemplo el siguiente objeto:

var foo = {
  first : 'Hello',
  second : 'World'
}

El objeto foo, además de sus correspondiente propiedades first y second, tiene asociado de forma ímplicita un prototipo. Por lo general, esta propiedad secreta no es accesible directamente por un programa ya que se trata de un valor interno del intérprete generado en tiempo de ejecución. Sin embargo, aunque no se ha estandarizado aún, los navegadores han comenzado a introducir un comando para exponer el valor del prototipo directamente: __proto__ (el nombre está compuesto por dos guiones bajos a cada lado).

Una alternativa natural del lenguaje que evita el uso de __proto__, válida únicamente mientras el constructor no se modifique es constructor.prototype:

console.log( a.__proto__ ); // Object{}
console.log( a.constructor.prototype ) // Object{}

NOTA: Para un análisis de cómo implementar una solución multinavegador para un método getPrototypeOf, podemos revisar el artículo homónimo de John Resig: Object.getPrototypeOf().

Por lo tanto, volviendo al primer ejemplo, tenemos el siguiente esquema:

Concepto de Herencia

Una vez que tenemos definido los objetos que componen una aplicación, es frecuente encontrar que varios de ellos comparten métodos y propiedades. Lógicamente, para aplicar una buena lógica de diseño DRY (Don’t Repeat Yourself), es importante extraer esos elementos comunes para que, aquellos que participen de ellos, puedan heredarlos. Este principio básico de reusabilidad de código es, en los sistemas tradicionales de clases, lo que llamamaos herencia basada en clases.

Sin embargo, Javascript no posee el concepto de clases sino de prototipos. Implementar un sistema de este tipo en Javascript es lo que conocemos como delegación basada en herencia o, siguiendo la nomenclatura recomendada por el ECMAScript, herencia prototípica.

Visión General

Tras la idea de la herencia, independientemente del sistema (clases o prototipos), consiste en declarar un objeto (o clase) cuyos métodos se heredan por referencia en otros objetos.

El sistema se comporta así del siguiente modo:

– Un objeto A posee un determinado número de métodos:

var a = {
  first : 'Hello',
  second : 'World',
  sum : function(x, y){
    return x + y;
  }
};

– Otros dos objetos, heredan las propiedades y métodos del primero a la vez que incorporan los suyos propios:

var b = {
  difference : function(x, y){
    return x - y;
  },
  __proto__ : a
};
 
var c = {
  product : function(x, y){
    return x * y;
  },
  __proto__ : a
};

– Una vez establecidas las relaciones, tanto b como c, participan (heredan) de a:

console.log( a.sum( 4, 6 ) ); // 10
console.log( b.sum( 3, 2 ) ); // 5
console.log( c.first ); // Hello

La herencia prototípica en cadena

La herencia en cadena proporciona una gran flexibilidad a la hora de recorrer prototipos.

El funcionamiento es el siguiente: si una propiedad o método no se encuentra en el propio objeto, se comienza a buscar dentro de la cadena prototípica. Primero se busca en su prototipo y, en caso de ser necesario, se avanza hacia el prototipo del prototipo de forma sucesiva.

Durante el recorrido, aquel primer método o propiedad coincidente, será el que se utilice. En caso de no encontrarse ninguno en toda la cadena, se devuelve el valor undefined.

Este comportamiento, similar al que encontramos en la herencia clásica, ofrece una gran flexibilidad a la hora de diseñar grandes arquitecturas.

Conclusión

Javascript no dispone el paradigma de clases a la hora de manejar objetos y presentar el concepto de herencia. Sin embargo, su modelo se basa en la idea de prototipos la cual permite una funcionalidad similar a la hora de reutilizar código y estructurar un diseño.Este esquema puede llegar incliso a ser más flexible que el tradicional en determinados aspectos.

Conocer el funcionamiento interna en la cadena prototípica permite diseñar arquitecturas complejas que deleguen propiedades y métodos entre si.

Más Información

Peter Michaux, Transitioning from Java Classes to JavaScript Prototypes

Douglas Crockford, Classical Inheritance in JavaScript

David Aurelio, Object-Based Inheritance For ECMAScript 5

John Resig, Simple JavaScript Inheritance

Más:

{8} Comentarios.

  1. jose

    Otro excelente artículo.

    Quiero plantear un tema. Para empezar quiero aclarar que no soy ningún programador experimentado y que llevo menos de un año trabajando para una empresa en desarrollo de aplicaciones web. Tengo conocimientos de POO (JAVA) y por cuestiones profesionales me he visto obligado a aprender Javascript.

    Javascript es un lenguaje de propósito especifico orientado para la web. Yo entiendo que se use para realizar una primera validación en los clientes y cuatro funcionalidades (paso parámetros, etc) que pueden resultar útiles. Pero ¿todo este auge de los frameworks? ¿todas estas «virguerías» para intentar emular el comportamiento de los lenguajes orientados a objetos clásicos? No sé, no lo termino de ver claro… Por ejemplo si queremos heredar ¿Por qué no implementan de una vez la palabra reservada Extends? Todo esto añadido a que estamos a merced de los navegadores y de que el código que programamos sea compatible en los más comunes.

    Todo esto lo planteo desde mi desconocimiento del lenguaje javascript; y es que es un poco frustrante cuando dominas un lenguaje como java y ves que tienes que volver a aprender a aplicar conceptos que conoces.

    • Carlos Benítez

      Hola Jose;
      tu comentario me parece interesante pero tengo que estar en completo desacuerdo. JavaScript no es un lenguaje orientado a web entendiendo por esto que hablamos de un lenguaje menor o un ‘juguete’.

      JavaScript es un lenguaje moderno, orientado a objetos y con un alto nivel de abstracción. El uso de los frameworks no es para emular funcionalidades de otros lenguajes, sino para presentar las propias del mismo.

      JavaScript no es Java; es otro lenguaje nacido en el mismo contexto. Ambos vieron la luz en el mismo año y presentaron grandes novedades con respecto a la por entonces dominante familia C. Java optó por un enfoque basado en clases mientras que JavaScript lo hizo con el original contexto de los prototipos. Esto obligó a que ambos lenguajes siguieran desde ese momento caminos diferentes en cuanto a la implementación de lo que se llamó soluciones de tercera generación en cuanto a desarrollo del software: los objetos.

      Efectivamente, hay que volver a aprender lo que ya sabes: es lógico. No existe un estándar en cuanto a la definición de los lenguajes de programación. Igual que para el paso de Java a Ruby tienes que aprender otros conceptos (o a Haskell, o a PHP mismo), en JavaScript ocurre igual.

      Saludos!

  2. jose

    Afirmo que Javascript es un lenguaje orientado a web porque desconozco otro ámbito en el que se use que no sea el de desarrollo de aplicaciones web.

    Y mi crítica hacia Javascript es más por el hecho de que hayan aparecido tantos frameworks, en vez de mejorar su base (la del lenguaje Javascript). Solucionar el tipado blando, simplificar el manejo de Ajax, algunas funciones poco intuitivas, etc. creo que sería más beneficioso que crear pseudolenguajes que precisamente intentan corregir eso.

    • Carlos Benítez

      JavaScript no es sólo un lenguaje de aplicaciones web. Es cierto que nació con esa intención pero, dada precisamente su flexibilidad y arquitectura, podemos utilizarlo como lenguaje de escritorio: el proyecto Gnome es una prueba de ello con aplicaciones como Seed o Gjs.

      También lo encontramos en aplicaciones para dispositivos móviles que actúan como si fuesen nativas: Android, iOS, Symbian… Para completar este escenario, no podemos tampoco olvidar el paradigma de ejecución en servidor con NodeJS o Rhino entre otras implementaciones.

      En cuanto a los pseudolenguajes, realmente los tendríamos en todos los lenguajes: Ruby On Rails no hace sino aplicar una capa más a Ruby sin alterar su núcleo; en PHP tenemos cientos de frameworks para expandir su manejo nativo de objetos; en Java, Spring o ZK hacen más o menos lo mismo…

      No se trata de solucionar el tipado blando: el tipado blando no es un error, es una ‘feature’ del lenguaje que bien utilizada resulta muy potente; las funciones poco intuitivas se resuelven leyendo la especificación ES5, etc…

      No tenemos que empeñarnos en convertir a JavaScript en un lenguaje que no es: no es Java, ni C. Los frameworks permiten trabajar con una capa intermedia para que se parezca, pero la verdadera potencia del lenguaje está en sus propios términos y características.

      Saludos!

  3. jose

    Gracias por tus repuestas.

    Supongo que al final es lo de siempre: el mejor lenguaje es el que dominas. Espero que con el tiempo pueda apreciar todas las bondades de Javascript.

    • Carlos Benítez

      Siempre suelo decir que no hay lenguajes buenos o malos; es el uso que cada desarrollador hace del mismo lo que determina la calidad final de un producto.
      Lo que si es innegable, es que para obtener ese resultado de calidad, es necesario conocer sus particularidades. Por eso mismo, como bien comentas, tu mejor resultado será siempre sobre el lenguaje que mejor dominas.

      Saludos!!

  4. Arturo

    Hola, muy bueno tu articulo, tengo una dudas nose si podrIas ayudarme.

    1. Una funcion constructora al ser un objeto tiene tambien un propiedad prototype, pero por ejemplo si tuvieramos la funcion constructora:

    function Foo(){}

    Al hacer

    Foo.prototype.construtor

    en la consola de javascript de chrome me indica que su contructor es la misma funcion constructora Foo().

    No entiendo la logica de esto.

    Saludos

  5. Macroweb

    excelente aporte solo que llevo ya rato intentando trabajar con los objetos y prototipados al estilo JSON en javascript pero nunca avía oído hablar de esta propiedad __proto__ muchas gracias un buen aporte a mis conocimientos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *