Как получить доступ к «этому» внутри обратного вызова
ключевое слово JavaScript this
является источником боли для многих людей. Обычный вопрос звучит примерно так:
«Я вызываю функцию Firebase, и внутри этого обратного вызова мне нужно получить доступ к this, чтобы обновить мой компонент React».
Другой распространенный случай — setTimeout:
«Проблема в том, что setTimeout принимает обратный вызов, но мне нужно получить к нему доступ, а setTimeout изменит его!»
Основное руководство
В обычных условиях, this
относится ко всему, что находится слева от точки и время звонка. Есть несколько исключений, которые специально изменяют поведение этого—call
, apply
а также bind
. Если вы не использовали одну из этих функций для изменения поведения this, посмотрите слева от точки во время вызова.
Пример
var alarm = {
ringer: function () {
console.log("The " + this.color + " alarm: Ring!!!");
},
color: "red"
}
alarm.ringer();
Это довольно прямолинейно. Когда вы звоните alarm.ringer();
слева от точки alarm
. Итак, внутри функции звонка this
это сигнал тревоги и цвет красный.
Ошибка в обратном вызове setTimeout
var alarm = {
ringer: function () {
console.log("The " + this.color + " alarm: Ring!!!");
},
set: function (milliseconds) {
setTimeout(function () { this.ringer() }, milliseconds);
},
color: "red"
}
alarm.set(2000);
Цель здесь заключалась в том, чтобы будильник зазвонил через две секунды. К сожалению, мы получаем эту ошибку:
Причина в том, что setTimeout вызывает переданный в него обратный вызов. Судя по ошибке, мы видим, что функция setTimeout создала Timeout
объект и сохранил функцию обратного вызова, которую мы передали в него, как Timeout._onTimeout
. А также в то время это называлось то, что слева от точки, — это тайм-аут, а не тревога.
Классическое решение
Традиционно проблема «этого» решалась путем присвоения «этого» из внешней области видимости новой переменной с именем «_this» или «that».
var alarm = {
ringer: function () {
console.log("The " + this.color + " alarm: Ring!!!");
},
set: function (milliseconds) {
var that = this;
setTimeout(function () { that.ringer() }, milliseconds);
},
color: "red"
}
alarm.set(2000);
Теперь это работает. Поскольку значение this внутри set
функция-будильник, и that
просто обычная переменная. Поскольку функции могут обращаться к переменным из окружающей их области видимости, that.ringer()
внутри обратного вызова alarm.ringer()
.
The red alarm: Ring!!!
Решение стрелочной функции ES6
Стрелочные функции ES6 не работают точно так же как обычные функциональные выражения. У них нет собственных привязок к «этому», поэтому любые ссылки на «это» используют привязки из закрывающей функции.
let alarm = {
ringer: function () {
console.log("The " + this.color + " alarm: Ring!!!");
},
set: function (milliseconds) {
setTimeout(() => { this.ringer() }, milliseconds);
},
color: "red"
}
alarm.set(2000);
Как упоминается в ссылке MDN выше, стрелочные функции плохо подходят для методов, но они представляют собой удобный способ упростить обратные вызовы. В 2019 году большинство разработчиков JS используют стрелочные функции для решения этой проблемы и сталкиваются только с шаблоном «это = то» в библиотеках и устаревшем коде.