У меня есть функция-конструктор, которая регистрирует обработчик событий:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
Решение проблемы
Что вы должны знать оthis
this
(он же «контекст») — это специальное ключевое слово внутри каждой функции, и его значение зависит только от того, как была вызвана функция, а не от того, как/когда/где она была определена. На него не влияют лексические области видимости, как на другие переменные (кроме стрелочных функций, см. ниже). Вот некоторые примеры:
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
Чтобы узнать больше this
, ознакомьтесь с документацией MDN.
Как обратиться к правильномуthis
Используйте стрелочные функции
ECMAScript 6 представил стрелочные функции, которые можно рассматривать как лямбда-функции. У них нет своей this
привязки. Вместо этого this
ищется в области видимости, как обычная переменная. Это означает, что вам не нужно звонить .bind
. Это не единственное особое поведение, которое у них есть, пожалуйста, обратитесь к документации MDN для получения дополнительной информации.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
Не используйтеthis
На самом деле вы не хотите обращаться this
к конкретному объекту, но к объекту, на который он ссылается. Вот почему простое решение — просто создать новую переменную, которая также ссылается на этот объект. Переменная может иметь любое имя, но распространенными являются self
и that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Поскольку self
это обычная переменная, она подчиняется правилам лексической области видимости и доступна внутри обратного вызова. Это также имеет то преимущество, что вы можете получить доступ к this
значению самого обратного вызова.
Явный набор this
обратного вызова - часть 1
Может показаться, что у вас нет контроля над значением, this
потому что оно устанавливается автоматически, но на самом деле это не так.
У каждой функции есть метод .bind
[docs], который возвращает новую функцию this
, привязанную к значению. Функция ведет себя точно так же, как та, которую вы вызвали .bind
, только this
она была задана вами. Независимо от того, как и когда эта функция вызывается, this
она всегда будет ссылаться на переданное значение.
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
В этом случае мы привязываем callback this
к значению MyConstructor
s this
.
Примечание. В качестве контекста привязки для jQuery используйте вместо этого jQuery.proxy
[docs]. Причина этого в том, что вам не нужно сохранять ссылку на функцию при отвязывании обратного вызова события. jQuery обрабатывает это внутри.
Набор this
обратного звонка - часть 2
Некоторые функции/методы, которые принимают обратные вызовы, также принимают значение, на которое this
должны ссылаться обратные вызовы. Это в основном то же самое, что и привязка самостоятельно, но функция/метод делает это за вас. Array#map
[docs] является таким методом. Его подпись:
array.map(callback[, thisArg])
Первый аргумент — это обратный вызов, а второй аргумент — это значение, на которое this
следует ссылаться. Вот надуманный пример:
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
Примечание. Возможность передачи значения this
обычно упоминается в документации по этой функции/методу. Например, метод jQuery [docs]$.ajax
описывает параметр с именем context
:
Этот объект будет сделан контекстом всех обратных вызовов, связанных с Ajax.
Распространенная проблема: использование методов объекта в качестве обратных вызовов/обработчиков событий.
Другим распространенным проявлением этой проблемы является использование метода объекта в качестве обработчика обратного вызова/события. Функции — это граждане первого класса в JavaScript, а термин «метод» — это просто разговорный термин для функции, которая является значением свойства объекта. Но эта функция не имеет конкретной ссылки на «содержащий» ее объект.
Рассмотрим следующий пример:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
The function this.method
is assigned as click event handler, but if the document.body
is clicked, the value logged will be undefined
, because inside the event handler, this
refers to the document.body
, not the instance of Foo
.
As already mentioned at the beginning, what this
refers to depends on how the function is called, not how it is defined.
If the code was like the following, it might be more obvious that the function doesn't have an implicit reference to the object:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
The solution is the same as mentioned above: If available, use .bind
to explicitly bind this
to a specific value
document.body.onclick = this.method.bind(this);
или явно вызвать функцию как «метод» объекта, используя анонимную функцию в качестве обработчика обратного вызова/события и присвоив объект ( this
) другой переменной:
var self = this;
document.body.onclick = function() {
self.method();
};
или используйте функцию стрелки:
document.body.onclick = () => this.method();
Комментариев нет:
Отправить комментарий