javascript - objetos - Asignación de métodos prototipo*dentro*de la función constructora: ¿por qué no?
prototype javascript ejemplos (6)
Funcionalmente, ¿hay algún inconveniente para estructurar mi código de esta manera? ¿Agregar un método prototípico a un objeto prototipo dentro del cuerpo de la función constructora (es decir, antes de que se cierre la declaración de expresión de la función constructora) causará problemas de alcance inesperados?
Sí, hay inconvenientes y problemas de alcance inesperados.
-
Asignando el prototipo una y otra vez a una función definida localmente, ambos repiten esa asignación y crean un nuevo objeto de función cada vez. Las asignaciones anteriores serán basura recolectada ya que ya no se hace referencia a ellas, pero es un trabajo innecesario tanto en la ejecución en tiempo de ejecución del constructor como en términos de recolección de basura en comparación con el segundo bloque de código.
-
Hay problemas de alcance inesperados en algunas circunstancias. Vea el ejemplo de
Counter
al final de mi respuesta para un ejemplo explícito. Si se refiere a una variable local del constructor del método prototipo, entonces su primer ejemplo crea un error potencialmente desagradable en su código.
Hay algunas otras diferencias (más menores). Su primer esquema prohíbe el uso del prototipo fuera del constructor como en:
Filter.prototype.checkProduct.apply(someFilterLikeObject, ...)
Y, por supuesto, si alguien usara:
Object.create(Filter.prototype)
sin ejecutar el constructor
Filter
, eso también crearía un resultado diferente que probablemente no sea tan probable ya que es razonable esperar que algo que usa el prototipo
Filter
ejecute el constructor
Filter
para lograr los resultados esperados.
Desde el punto de vista del rendimiento en tiempo de ejecución (rendimiento de los métodos de llamada en el objeto), sería mejor con esto:
var Filter = function( category, value ){
this.category = category;
this.value = value;
// product is a JSON object
this.checkProduct = function( product ){
// run some checks
return is_match;
}
};
Hay algunos "expertos" en Javascript que afirman que el ahorro de memoria del uso del prototipo ya no es necesario (vi un video sobre eso hace unos días), por lo que es hora de comenzar a usar el mejor rendimiento de los métodos directamente en el objeto. que el prototipo Todavía no sé si estoy listo para abogar por eso, pero fue un punto interesante en el que pensar.
La mayor desventaja de su primer método que se me ocurre es que es realmente muy fácil cometer un error de programación desagradable. Si crees que puedes aprovechar el hecho de que el método prototipo ahora puede ver las variables locales del constructor, rápidamente te dispararás en el pie tan pronto como tengas más de una instancia de tu objeto. Imagina esta circunstancia:
var Counter = function(initialValue){
var value = initialValue;
// product is a JSON object
Counter.prototype.get = function() {
return value++;
}
};
var c1 = new Counter(0);
var c2 = new Counter(10);
console.log(c1.get()); // outputs 10, should output 0
Demostración del problema: http://jsfiddle.net/jfriend00/c7natr3d/
Esto se debe a que, aunque parece que el método
get
forma un cierre y tiene acceso a las variables de instancia que son variables locales del constructor, en la práctica no funciona de esa manera.
Como todas las instancias comparten el mismo objeto prototipo, cada nueva instancia del objeto
Counter
crea una nueva instancia de la función
get
(que tiene acceso a las variables locales del constructor de la instancia recién creada) y la asigna al prototipo, por lo que ahora todas las instancias tener un método
get
que acceda a las variables locales del constructor de la última instancia creada.
Es un desastre de programación, ya que es probable que esto nunca sea lo que se pretendía y fácilmente podría ser un rasguño de cabeza para descubrir qué salió mal y por qué.
Estilísticamente, prefiero esta estructura:
var Filter = function( category, value ){
this.category = category;
this.value = value;
// product is a JSON object
Filter.prototype.checkProduct = function( product ){
// run some checks
return is_match;
}
};
A esta estructura:
var Filter = function( category, value ){
this.category = category;
this.value = value;
};// var Filter = function(){...}
Filter.prototype.checkProduct = function( product ){
// run some checks
return is_match;
}
Funcionalmente, ¿hay algún inconveniente para estructurar mi código de esta manera? ¿Agregar un método prototípico a un objeto prototipo dentro del cuerpo de la función constructora (es decir, antes de que se cierre la declaración de expresión de la función constructora) causará problemas de alcance inesperados?
He usado la primera estructura antes con éxito, pero quiero asegurarme de que no estoy preparándome para un dolor de cabeza de depuración o causando dolor y agravación a otros desarrolladores debido a malas prácticas de codificación.
El primer código de ejemplo pierde el propósito del prototipo. Volverá a crear el método checkProduct para cada instancia. Si bien se definirá solo en el prototipo y no consumirá memoria para cada instancia, aún llevará tiempo.
Si desea encapsular la clase, puede verificar la existencia del método antes de indicar el método checkProduct:
if(!Filter.prototype.checkProduct) {
Filter.prototype.checkProduct = function( product ){
// run some checks
return is_match;
}
}
Hay una cosa más que debes considerar. El cierre de esa función anónima ahora tiene acceso a todas las variables dentro del constructor, por lo que puede ser tentador acceder a ellas, pero eso lo llevará a un agujero de conejo, ya que esa función solo tendrá acceso al cierre de una sola instancia. En su ejemplo, será la última instancia, y en mi ejemplo será la primera.
En el primer ejemplo, el prototipo de
Filter
no se llena de funciones hasta que se invoca al menos una vez.
¿Qué pasa si alguien intenta heredar el
Filter
prototípicamente?
Usando cualquiera de los nodejs ''
function ExtendedFilter() {};
util.inherit(ExtendedFilter, Filter);
o
Object.create
:
function ExtendedFilter() {};
ExtendedFilter.prototype = Object.create(Filter.prototype);
siempre termina con un prototipo vacío en la cadena del prototipo si olvidó o no supo invocar primero el
Filter
.
La mayor desventaja de su código es la posibilidad de cerrar para anular sus métodos.
Si escribo
Filter.prototype.checkProduct = function( product ){
// run some checks
return different_result;
}
var a = new Filter(p1,p2);
a.checkProduct(product);
El resultado será diferente al esperado ya que se llamará a la función original, no a my.
Si bien las otras respuestas se han centrado en las cosas que están mal al asignar al prototipo desde el interior del constructor, me centraré en su primera declaración:
Estilísticamente, prefiero esta estructura
Probablemente le guste la
encapsulation
limpia que ofrece esta notación: todo lo que pertenece a la clase está debidamente "definido" por el bloque
{}
.
(por supuesto, la falacia es que se
limita
a cada
ejecución
de la función constructora).
Le sugiero que tome los patrones de módulo (reveladores) que ofrece JavaScript. Obtiene una estructura mucho más explícita, una declaración de constructor independiente, variables privadas con ámbito de clase y todo encapsulado correctamente en un bloque:
var Filter = (function() {
function Filter(category, value) { // the constructor
this.category = category;
this.value = value;
}
// product is a JSON object
Filter.prototype.checkProduct = function(product) {
// run some checks
return is_match;
};
return Filter;
}());
Solo para tu información, tampoco puedes hacer esto de manera segura:
function Constr(){
const privateVar = ''this var is private'';
this.__proto__.getPrivateVar = function(){
return privateVar;
};
}
la razón es porque
Constr.prototype === this.__proto__
, por lo que tendrá el mismo mal comportamiento.