Los prototipos son una optimización .
Un gran ejemplo de cómo usarlos bien es la biblioteca jQuery. Cada vez que obtiene un objeto jQuery utilizando $('.someClass')
, ese objeto tiene docenas de "métodos". La biblioteca podría lograr eso devolviendo un objeto:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
Pero eso significaría que cada objeto jQuery en la memoria tendría docenas de ranuras con nombre que contienen los mismos métodos, una y otra vez.
En cambio, esos métodos se definen en un prototipo y todos los objetos jQuery "heredan" ese prototipo para obtener todos esos métodos a un costo de ejecución muy bajo.
Una parte de vital importancia de cómo jQuery lo hace bien es que esto está oculto al programador. Se trata simplemente como una optimización, no como algo de lo que tenga que preocuparse cuando use la biblioteca.
El problema con JavaScript es que las funciones de constructor desnudas requieren que la persona que llama recuerde ponerles un prefijo new
o, de lo contrario, normalmente no funcionan. No hay una buena razón para ello. jQuery lo hace bien al ocultar esas tonterías detrás de una función ordinaria $
, por lo que no tiene que preocuparse de cómo se implementan los objetos.
Para que pueda crear cómodamente un objeto con un prototipo específico, ECMAScript 5 incluye una función estándar Object.create
. Una versión muy simplificada se vería así:
Object.create = function(prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
};
Simplemente se encarga del dolor de escribir una función constructora y luego llamarla con new
.
¿Cuándo evitarías los prototipos?
Una comparación útil es con lenguajes de OO populares como Java y C #. Estos admiten dos tipos de herencia:
- interfaz de la herencia, en el que
implement
un interface
tal que la clase proporciona su propia implementación única para cada miembro de la interfaz.
- aplicación de herencia, en el que
extend
una class
que proporciona implementaciones por defecto de algunos métodos.
En JavaScript, la herencia prototípica es una especie de herencia de implementación . Entonces, en aquellas situaciones en las que (en C # o Java) habría derivado de una clase base para obtener el comportamiento predeterminado, al que luego realiza pequeñas modificaciones a través de anulaciones, luego en JavaScript, la herencia prototípica tiene sentido.
Sin embargo, si se encuentra en una situación en la que habría utilizado interfaces en C # o Java, entonces no necesita ninguna característica de lenguaje en particular en JavaScript. No es necesario declarar explícitamente algo que represente la interfaz, y no es necesario marcar los objetos como "implementando" esa interfaz:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
En otras palabras, si cada "tipo" de objeto tiene sus propias definiciones de los "métodos", entonces no tiene ningún valor heredar de un prototipo. Después de eso, depende de cuántas instancias asigne de cada tipo. Pero en muchos diseños modulares, solo hay una instancia de un tipo determinado.
Y de hecho, muchas personas han sugerido que la herencia de implementación es mala . Es decir, si hay algunas operaciones comunes para un tipo, entonces tal vez sea más claro si no se colocan en una clase base / super, sino que se exponen como funciones ordinarias en algún módulo, al que se le pasa el objeto (s) desea que operen.