JavaScript Callback Scope
-
06-07-2019 - |
Вопрос
У меня возникли проблемы с простым старым 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?
Решение
(извлечены некоторые объяснения, которые были скрыты в комментариях в другом ответе)
Проблема заключается в следующей строке:
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;
};
}
Другие советы
this.dom.addEventListener("click", function(event) {
self.onclick(event)
}, false);
Для jQuery пользователей, которые ищут решение этой проблемы, вы должны использовать jQuery.proxy р>
Объяснение заключается в том, что self.onclick
не означает, что вы думаете, что это означает в JavaScript. Фактически это означает функцию onclick
в прототипе объекта self
(никоим образом не ссылаясь на сам self
).
JavaScript имеет только функции и не имеет делегатов, таких как C #, поэтому невозможно передать метод И объект, к которому он должен быть применен, в качестве обратного вызова.
Единственный способ вызвать метод в обратном вызове - это вызвать его самостоятельно внутри функции обратного вызова. Поскольку функции JavaScript являются замыканиями, они могут получить доступ к переменным, объявленным в области, в которой они были созданы.
var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);
Хорошее объяснение проблемы (у меня были проблемы с пониманием решений, описанных до сих пор): доступно здесь .
это один из самых запутанных моментов JS: переменная 'this' означает самый локальный объект ... но функции также являются объектами, так что 'this' указывает на это. Есть и другие тонкие моменты, но я не помню их всех.
Я обычно избегаю использования 'this', просто определяю локальную переменную 'me' и использую ее вместо этого.