Preservando uma referência a “isso” em funções de protótipo JavaScript

Eu estou apenas começando a usar JavaScript prototípico e estou tendo problemas para descobrir como preservar this referência ao object principal de dentro de uma function de protótipo quando o escopo muda. Deixe-me ilustrar o que quero dizer (estou usando o jQuery aqui):

 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(); 

Eu sei que posso preservar uma referência ao object principal fazendo isso no começo do myfunc :

 var myThis = this; 

e, em seguida, usando myThis.myValue para acessar a propriedade do object principal. Mas o que acontece quando eu tenho um monte de funções protótipo no MyClass ? Eu tenho que salvar a referência para this no começo de cada um? Parece que deveria haver um caminho mais limpo. E que tal uma situação 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(); 

Nesse caso, não consigo criar uma referência ao object principal com var myThis = this; porque mesmo o valor original this dentro do contexto do doSomething é um object jQuery e não um object MyClass .

Foi sugerido que eu usasse uma variável global para manter a referência ao original, mas isso parece uma idéia muito ruim para mim. Eu não quero poluir o namespace global e parece que isso me impediria de instanciar dois objects MyClass diferentes sem que eles interfiram uns com os outros.

Alguma sugestão? Existe uma maneira limpa de fazer o que eu estou procurando? Ou todo o meu padrão de design é falho?

    Para preservar o contexto, o método bind é realmente útil, agora é parte da recém-lançada ECMAScript 5th Edition Specification, a implementação desta function é simples (apenas 8 linhas):

     // 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))); }; }; } 

    E você poderia usá-lo, no seu exemplo assim:

     MyClass.prototype.myfunc = function() { this.element.click((function() { // ... }).bind(this)); }; 

    Outro exemplo:

     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); 

    Neste segundo exemplo, podemos observar mais sobre o comportamento do bind .

    Basicamente gera uma nova function, que será o responsável de chamar nossa function, preservando o contexto da function ( this valor), que é definido como o primeiro argumento do bind .

    Os demais argumentos são simplesmente passados ​​para nossa function.

    Note neste exemplo que a function fx1 , é invocada sem qualquer contexto de object ( obj.method() ), apenas como uma chamada de function simples, neste tipo de invokation, a palavra chave this irá se referir ao object Global, ele irá alertar “teste global”.

    Agora, o fx2 é a nova function que o método bind gerou, ele irá chamar nossa function preservando o contexto e passando corretamente os argumentos, ele irá alertar “obj test 1, 2, 3, 4, 5” porque nós o invocamos sumndo o dois argumentos adicionais, ele já havia vinculado os três primeiros.

    Para o seu último exemplo MyClass , você poderia fazer isso:

     var myThis=this; this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); }); 

    Na function que é passada para each , this se refere a um object jQuery, como você já sabe. Se dentro dessa function você obtiver a function doSomething do myThis , e então chamar o método apply nessa function com o array arguments (veja a function apply e a variável arguments ), então this será configurado como myThis no doSomething .

    Eu percebo que este é um segmento antigo, mas eu tenho uma solução que é muito mais elegante, e tem poucas desvantagens para além do fato de que geralmente não é feito, como eu notei.

    Considere o seguinte:

     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 

    Na function acima, definimos uma variável local (contexto). Em seguida, adicionamos uma function prototípica (teste) que retorna a variável local. Como você provavelmente previu, quando criamos uma instância dessa function e depois executamos o método de teste, ela não retorna a variável local porque quando definimos a function prototípica como um membro para a nossa function principal, ela estava fora do escopo onde a function variável local é definida. Este é um problema geral com a criação de funções e a adição de protótipos a ele – você não pode acessar nada que tenha sido criado no escopo da function principal.

    Para criar methods que estão dentro do escopo da variável local, precisamos defini-los diretamente como membros da function e nos livrar da referência 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; 

    Você pode estar preocupado porque, como os methods não estão sendo criados de forma prototípica, instâncias diferentes podem não ser separadas por dados. Para demonstrar que eles são, considere isto:

     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 

    Outra maneira de usar esse método é criar singletons. Mais frequentemente, nossas funções javascript não estão sendo instanciadas mais de uma vez. Se você sabe que nunca precisará de uma segunda instância da mesma function, então existe uma maneira abreviada de criá-las. Esteja avisado, no entanto: lint irá reclamar que é uma construção estranha, e questionar o uso da palavra-chave ‘new’:

     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: Os benefícios de usar esse método para criar objects de function são abundantes.

    • Isso torna seu código mais fácil de ler, uma vez que ele recorta os methods de um object de function de uma maneira que o torna visualmente mais fácil de ser seguido.
    • Ele permite access às variables ​​definidas localmente somente em methods originalmente definidos dessa maneira, mesmo que você adicione funções prototípicas ou mesmo funções membro ao object-function, ele não pode acessar as variables ​​locais e qualquer funcionalidade ou dados que você armazene nesse nível permanece seguro e inacessível de qualquer outro lugar.
    • Permite uma maneira simples e direta de definir singletons.
    • Ele permite que você armazene uma referência a ‘this’ e mantenha essa referência indefinidamente.

    Con’s: Existem algumas desvantagens em usar este método. Eu não pretendo ser abrangente 🙂

    • Como os methods são definidos como membros para o object e não como protótipos – a inheritance pode ser obtida usando a definição do membro, mas não as definições prototípicas. Isso está realmente incorreto. A mesma inheritance prototípica pode ser obtida agindo em f.constructor.prototype .

    Você pode definir o escopo usando as funções call () e apply ()

    Como você está usando o jQuery, é importante notar que this já é mantido pelo próprio jQuery:

     $("li").each(function(j,o){ $("span", o).each(function(x,y){ alert(o + " " + y); }); }); 

    Neste exemplo, o representa o li , enquanto y representa o span filho. E com $.click() , você pode obter o escopo do object de event :

     $("li").click(function(e){ $("span", this).each(function(i,o){ alert(e.target + " " + o); }); }); 

    Onde e.target representa o li e o representa o span filho.

    Você pode criar uma referência ao object this ou usar o método with (this) . O último é extremamente útil quando você usa manipuladores de events e não tem como passar uma referência.

     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 } }); } 

    Outra solução (e minha maneira favorita no jQuery) é usar o jQuery fornecido ‘e.data’ para passar ‘this’. Então você pode fazer isso:

     this.element.bind('click', this, function(e) { e.data.myValue; //e.data now references the 'this' that you want });