Область обратного вызова JavaScript

У меня возникают проблемы с обычным старым 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?

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);

Ответ 5

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

Ответ 6

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

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

jquery.callback

Ответ 7

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

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