GOOGLE ADS

вторник, 10 мая 2022 г.

Как получить доступ к правильному this внутри обратного вызова

У меня есть функция-конструктор, которая регистрирует обработчик событий:


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к значению MyConstructors 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();

Комментариев нет:

Отправить комментарий

Laravel Datatable addColumn returns ID of one record only

Я пытаюсь использовать Yajra Datatable для интеграции DataTable на свой веб-сайт. Я смог отобразить таблицу, но столкнулся с проблемой. В по...