JavaScript no posee en sus características un sistema clásico de herencia, sino que utiliza un prototipo para esto.
Si bien a menudo se considera uno de los puntos débiles de JavaScript, el modelo de herencia prototipado es de hecho más poderoso que el modelo clásico. Por ejemplo, es bastante trivial construir un modelo clásico a partir del modelo prototipado, mientras que al contrario es una tarea mucho más difícil.
Debido al hecho que JavaScript es básicamente el único lenguaje que utiliza ampliamente la herencia prototipada, se necesita algo de tiempo para adaptarse a las diferencias entre los dos modelos.
La primera gran diferencia es que la herencia en JavaScript se realiza usando llamadas de cadenas de prototipo (prototype chains).
Nota: Simplemente usando
Bar.prototype = Foo.prototype
dará lugar a dos objetos que comparten el mismo prototipo. Por lo tanto, los cambios que se realicen en un objeto afectará al otro objeto, así, en la mayoría de los casos no es el efecto deseado.
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// Asigna el prototipo de Bar como una nueva instancia de Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// Asegura que el constructor sea Bar
Bar.prototype.constructor = Bar;
var test = new Bar() // crea una nueva instancia de Bar
// Resultado de cadena de prototipos (prototype chain)
test [instance of Bar]
Bar.prototype [instance of Foo]
{ foo: 'Hello World', value: 42 }
Foo.prototype
{ method: ... }
Object.prototype
{ toString: ... /* etc. */ }
En el código anterior, el objeto test
hereda de Bar.prototype
y Foo.prototype
;
por lo tanto, tendrá acceso a la función method
que se ha definido en Foo
.
También se tendrá acceso a la propiedad value
de la única instancia de Foo
que compone su prototipo. Es importante tomar en cuenta que new Bar()
no creará una nueva
instancia de Foo
, pero retornará lo asignado en su prototipo; de este modo, todas las instancias
de Bar
tendrán que compartir el mismo valor
de la propiedad.
Nota: No utilice
Bar.prototype = Foo
, ya que no apunta al prototipo deFoo
, sino al objeto de la funciónFoo
. Así la cadena de prototipo cambiará aFunction.prototype
y no aFoo.prototype
; Por lo tanto, elmétodo
no estará disponible en la cadena de prototipo.
Cuando se accede a las propiedades de un objeto, JavaScript recorre la cadena de prototipo hacia arriba hasta encontrar la propiedad con el nombre solicitado.
Cuando se llega al final de la cadena - concretamente Object.prototype
- y aún
no se ha encontrado la propiedad especificada, se retornará un valor
undefined en su lugar.
Aunque la propiedad prototype es usada por el lenguaje para construir la cadena de prototipos, es posible asignar cualquier valor. Aunque los tipos primitivos serán ignorados cuando se asigne en prototype.
function Foo() {}
Foo.prototype = 1; // no tendrá efecto
La asignación de objetos, como se muestra en el ejemplo anterior, funcionará, y permitirá la creación dinámica de cadena de prototipos.
El tiempo tomado en la búsqueda de propiedades es alta y la cadena de prototipo puede presentar un impacto negativo crítico en el rendimiento en partes del código. Además, si ha tratado de acceder a propiedades que no existen, esto provoca que se recorra la cadena de prototipo completa.
Además, al recorrer en iteración las propiedades de un objeto, cada propiedad encontrada en la cadena de prototipo será enumerada.
Una mala característica que se suele utilizar para extender Object.prototype
o cualquier
otro prototipo construido.
Esta técnica es conocida en inglés como monkey patching y rompe la encapsulación del código. Si bien es utilizado en frameworks como Prototype, todavía no existen buenas razones para adoptarlo o integrarlo como tipos de dato o como funcionalidad no estándar.
La única razón coherente para extender un prototipo es para adaptarle nuevas
características de los motores JavaScript más modernos; por ejemplo,
Array.forEach
.
Se debe entender por completo el modelo de herencia prototipado antes de escribir código complejo que lo utilice. Además, observe la longitud de la cadena de prototipo y modifíquela si es necesario para evitar posibles problemas de rendimiento. Con relación a los prototipos nativos, estos nunca deben ser extendidos a menos que sea para mantener la compatibilidad con nuevas características de JavaScript.