oriented - Preservar una referencia a "esto" en las funciones del prototipo JavaScript
object oriented programming create a basic javascript object (7)
Dado que está utilizando jQuery, vale la pena señalar que this
ya lo mantiene jQuery:
$("li").each(function(j,o){
$("span", o).each(function(x,y){
alert(o + " " + y);
});
});
En este ejemplo, o
representa el li
, mientras que y
representa el span
hijos. Y con $.click()
, puede obtener el alcance del objeto de event
:
$("li").click(function(e){
$("span", this).each(function(i,o){
alert(e.target + " " + o);
});
});
Donde e.target
representa el li
, y o
representa el span
hijos.
Esta pregunta ya tiene una respuesta aquí:
Estoy empezando a utilizar JavaScript prototípico y tengo problemas para encontrar una forma de preservar this
referencia al objeto principal desde el interior de una función de prototipo cuando cambia el alcance. Permítanme ilustrar lo que quiero decir (estoy usando jQuery aquí):
MyClass = function() {
this.element = $(''#element'');
this.myValue = ''something'';
// some more code
}
MyClass.prototype.myfunc = function() {
// at this point, "this" refers to the instance of MyClass
this.element.click(function() {
// at this point, "this" refers to the DOM element
// but what if I want to access the original "this.myValue"?
});
}
new MyClass();
Sé que puedo preservar una referencia al objeto principal al hacer esto al comienzo de myfunc
:
var myThis = this;
y luego usar myThis.myValue
para acceder a la propiedad del objeto principal. ¿Pero qué sucede cuando tengo un montón de funciones prototipo en MyClass
? ¿Tengo que guardar la referencia a this
al comienzo de cada uno? Parece que debería haber una manera más limpia. ¿Y una situación como esta?
MyClass = function() {
this.elements $(''.elements'');
this.myValue = ''something'';
this.elements.each(this.doSomething);
}
MyClass.prototype.doSomething = function() {
// operate on the element
}
new MyClass();
En ese caso, no puedo crear una referencia al objeto principal con var myThis = this;
porque incluso el valor original de this
en el contexto de doSomething
es un objeto jQuery
y no un objeto MyClass
.
Se me ha sugerido que use una variable global para mantener la referencia al original, pero a mí me parece una idea realmente mala. No quiero contaminar el espacio de nombres global y parece que me impediría instanciar dos objetos MyClass
diferentes sin que interfieran entre sí.
¿Alguna sugerencia? ¿Hay una manera limpia de hacer lo que estoy buscando? ¿O es mi patrón de diseño completo defectuoso?
Me doy cuenta de que este es un hilo viejo, pero tengo una solución que es mucho más elegante, y tiene pocos inconvenientes, aparte del hecho de que generalmente no se hace, como he notado.
Considera lo siguiente:
var f=function(){
var context=this;
}
f.prototype.test=function(){
return context;
}
var fn=new f();
fn.test();
// should return undefined because the prototype definition
// took place outside the scope where ''context'' is available
En la función anterior definimos una variable local (contexto). Luego agregamos una función prototípica (prueba) que devuelve la variable local. Como probablemente haya predicho, cuando creamos una instancia de esta función y luego ejecutamos el método de prueba, no devuelve la variable local porque cuando definimos la función prototípica como un miembro de nuestra función principal, estaba fuera del alcance donde el variable local está definida. Este es un problema general con la creación de funciones y la adición de prototipos: no se puede acceder a nada creado en el ámbito de la función principal.
Para crear métodos que están dentro del alcance de la variable local, necesitamos definirlos directamente como miembros de la función y deshacernos de la referencia prototípica:
var f=function(){
var context=this;
this.test=function(){
console.log(context);
return context;
};
}
var fn=new(f);
fn.test();
//should return an object that correctly references ''this''
//in the context of that function;
fn.test().test().test();
//proving that ''this'' is the correct reference;
Es posible que le preocupe que, dado que los métodos no se crean de manera prototípica, es posible que diferentes instancias no estén separadas por datos. Para demostrar que lo son, considere esto:
var f=function(val){
var self=this;
this.chain=function(){
return self;
};
this.checkval=function(){
return val;
};
}
var fn1=new f(''first value'');
var fn2=new f(''second value'');
fn1.checkval();
fn1.chain().chain().checkval();
// returns ''first value'' indicating that not only does the initiated value remain untouched,
// one can use the internally stored context reference rigorously without losing sight of local variables.
fn2.checkval();
fn2.chain().chain().checkval();
// the fact that this set of tests returns ''second value''
// proves that they are really referencing separate instances
Otra forma de utilizar este método es crear singletons. En la mayoría de los casos, nuestras funciones de JavaScript no se crean más de una vez. Si sabe que nunca necesitará una segunda instancia de la misma función, entonces hay una forma abreviada de crearlos. Tenga cuidado, sin embargo: lint se quejará de que es una construcción extraña, y cuestionará su uso de la palabra clave ''nuevo'':
fn=new function(val){
var self=this;
this.chain=function(){
return self;
};
this.checkval=function(){
return val;
};
}
fn.checkval();
fn.chain().chain().checkval();
Pro''s: los beneficios de usar este método para crear objetos de función son abundantes.
- Hace que su código sea más fácil de leer, ya que sangra los métodos de un objeto de función de una manera que lo hace visualmente más fácil de seguir.
- Permite el acceso a las variables definidas localmente solo en métodos originalmente definidos de esta manera, incluso si luego agrega funciones prototípicas o incluso funciones de miembros al objeto función, no puede acceder a las variables locales y cualquier funcionalidad o datos que almacene en ese nivel permanece seguro e inaccesible desde cualquier otro lugar.
- Permite una forma simple y directa de definir singletons.
- Le permite almacenar una referencia a ''esto'' y mantener esa referencia indefinidamente.
Con: hay algunos inconvenientes al usar este método. No pretendo ser comprensivo :)
-
Debido a que los métodos se definen como miembros del objeto y no como prototipos, la herencia se puede lograr utilizando la definición de miembro pero no las definiciones prototípicas.Esto es realmente incorrecto La misma herencia prototípica se puede lograr actuando sobre elf.constructor.prototype
def.constructor.prototype
.
Otra solución (y mi forma favorita en jQuery) es usar jQuery proporcionado ''e.data'' para pasar ''this''. Entonces puedes hacer esto:
this.element.bind(''click'', this, function(e) {
e.data.myValue; //e.data now references the ''this'' that you want
});
Para preservar el contexto, el método de bind
es realmente útil, ahora es parte de la publicación de la 5ª Edición de la especificación ECMAScript , la implementación de esta función es simple (solo 8 líneas):
// The .bind method from Prototype.js
if (!Function.prototype.bind) { // check if native implementation available
Function.prototype.bind = function(){
var fn = this, args = Array.prototype.slice.call(arguments),
object = args.shift();
return function(){
return fn.apply(object,
args.concat(Array.prototype.slice.call(arguments)));
};
};
}
Y podrías usarlo, en tu ejemplo de esta manera:
MyClass.prototype.myfunc = function() {
this.element.click((function() {
// ...
}).bind(this));
};
Otro ejemplo:
var obj = {
test: ''obj test'',
fx: function() {
alert(this.test + ''/n'' + Array.prototype.slice.call(arguments).join());
}
};
var test = "Global test";
var fx1 = obj.fx;
var fx2 = obj.fx.bind(obj, 1, 2, 3);
fx1(1,2);
fx2(4, 5);
En este segundo ejemplo, podemos observar más sobre el comportamiento de bind
.
Básicamente genera una nueva función, que será la responsable de llamar a nuestra función, conservando el contexto de la función ( this
valor), que se define como el primer argumento de bind
.
El resto de los argumentos simplemente pasan a nuestra función.
Observe en este ejemplo que la función fx1
, se invoca sin ningún contexto de objeto ( obj.method()
), solo como una llamada a función simple, en este tipo de invocación, la palabra clave "dentro" se referirá al objeto Global, alertará "prueba global".
Ahora, el fx2
es la nueva función que generó el método bind
, llamará a nuestra función preservando el contexto y pasando correctamente los argumentos, alertará "obj test 1, 2, 3, 4, 5" porque lo invocamos agregando el dos argumentos adicionales, ya había vinculado los tres primeros.
Para su último ejemplo de MyClass
, podría hacer esto:
var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
En la función que se pasa a each
, this
refiere a un objeto jQuery, como ya sabes. Si dentro de esa función obtienes la función myThis
de myThis
, y luego llamas al método apply sobre esa función con la matriz arguments (mira la función apply
y la variable arguments
), this
se establecerá en myThis
in myThis
.
Puede crear una referencia al objeto this o puede usar el método with (this)
. Lo último es extremadamente útil cuando usa controladores de eventos y no tiene forma de pasar una referencia.
MyClass = function() {
// More code here ...
}
MyClass.prototype.myfunc = function() {
// Create a reference
var obj = this;
this.element.click(function() {
// "obj" refers to the original class instance
with (this){
// "this" now also refers to the original class instance
}
});
}
Puede establecer el alcance utilizando las funciones call () y apply ()