JavaScript Callback Scope - PullRequest
       44

JavaScript Callback Scope

58 голосов
/ 08 октября 2008

У меня возникли проблемы с простым старым JavaScript (без фреймворков) при ссылке на мой объект в функции обратного вызова.

function foo(id) {
    this.dom = document.getElementById(id);
    this.bar = 5;
    var self = this;
    this.dom.addEventListener("click", self.onclick, false);
}

foo.prototype = {
    onclick : function() {
        this.bar = 7;
    }
};

Теперь, когда я создаю новый объект (после загрузки DOM, с тестом span #)

var x = new foo('test');

'this' внутри функции onclick указывает на тест span #, а не на объект foo.

Как получить ссылку на мой объект foo внутри функции onclick?

Ответы [ 7 ]

81 голосов
/ 11 октября 2008

(извлечено какое-то объяснение, которое было скрыто в комментариях в другом ответе)

Проблема заключается в следующей строке:

this.dom.addEventListener("click", self.onclick, false);

Здесь вы передаете функциональный объект для использования в качестве обратного вызова. Когда вызывается событие, вызывается функция, но теперь она не имеет связи ни с одним объектом (этим).

Эту проблему можно решить, поместив функцию (со ссылкой на объект) в замыкание следующим образом:

this.dom.addEventListener(
  "click",
  function(event) {self.onclick(event)},
  false);

Поскольку переменной self было присвоено this при создании замыкания, функция замыкания запоминает значение переменной self, когда она вызывается позднее.

Альтернативный способ решить эту проблему - создать вспомогательную функцию (и избегать использования переменных для привязки this ):

function bind(scope, fn) {
    return function () {
        fn.apply(scope, arguments);
    };
}

Обновленный код будет выглядеть так:

this.dom.addEventListener("click", bind(this, this.onclick), false);

Function.prototype.bind является частью ECMAScript 5 и предоставляет те же функциональные возможности. Так что вы можете сделать:

this.dom.addEventListener("click", this.onclick.bind(this), false);

Для браузеров, которые еще не поддерживают ES5, MDN предоставляет следующую прокладку :

if (!Function.prototype.bind) {  
  Function.prototype.bind = function (oThis) {  
    if (typeof this !== "function") {  
      // closest thing possible to the ECMAScript 5 internal IsCallable function  
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
    }  

    var aArgs = Array.prototype.slice.call(arguments, 1),   
        fToBind = this,   
        fNOP = function () {},  
        fBound = function () {  
          return fToBind.apply(this instanceof fNOP  
                                 ? this  
                                 : oThis || window,  
                               aArgs.concat(Array.prototype.slice.call(arguments)));  
        };  

    fNOP.prototype = this.prototype;  
    fBound.prototype = new fNOP();  

    return fBound;  
  };  
} 
15 голосов
/ 08 октября 2008
this.dom.addEventListener("click", function(event) {
    self.onclick(event)
}, false);
5 голосов
/ 14 января 2011

Для пользователей jQuery , которые ищут решение этой проблемы, следует использовать jQuery.proxy

2 голосов
/ 27 ноября 2009

Хорошее объяснение проблемы (у меня были проблемы с пониманием решений, описанных ранее): доступно здесь .

2 голосов
/ 11 октября 2008

Объяснение состоит в том, что self.onclick не означает, что вы думаете, это означает в JavaScript. На самом деле это означает функцию onclick в прототипе объекта self (без ссылки на саму self).

В JavaScript есть только функции и нет делегатов, таких как C #, поэтому невозможно передать метод И объект, к которому он должен быть применен, в качестве обратного вызова.

Единственный способ вызвать метод в обратном вызове - это вызвать его самостоятельно внутри функции обратного вызова. Поскольку функции JavaScript являются замыканиями, они могут получить доступ к переменным, объявленным в области, в которой они были созданы.

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);
1 голос
/ 17 апреля 2009

Я написал этот плагин ...

думаю, будет полезно

jquery.callback

0 голосов
/ 08 октября 2008

это одна из самых запутанных точек JS: переменная 'this' означает самый локальный объект ... но функции также являются объектами, так что 'this' указывает на это. Есть и другие тонкие моменты, но я не помню их всех.

Я обычно избегаю использования 'this', просто определяю локальную переменную 'me' и использую ее вместо этого.

...