При использовании таймеров в обработчиках событий нас поджидают проблемы с потерей контекста. Давайте посмотрим на примере.
Пусть у нас есть инпут:
<input id="elem" value="text">
Пусть по клику на этот инпут сработает анонимная функция и внутри этой функции запустится таймер, каждую секунду выводящий что-нибудь в консоль:
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
setInterval(function() {
console.log('!!!'); // что-нибудь выводим в консоль
}, 1000);
});
Пока все работает верно. Но пусть теперь
мы хотим выводить в консоль value
нашего инпута - нас ждет сюрприз: в консоль
будет выводится undefined
:
elem.addEventListener('click', function() {
setInterval(function() {
console.log(this.value); // будет выводится undefined
}, 1000);
});
Все дело в том, что у нас получается функция
в функции: есть внешняя анонимная функция,
которая вызывается по клику и внутренняя
анонимная функция, которую запускает таймер.
Во внешней функции this
указывает
на инпут, но во внутренней - нет. Имеет место
потеря контекста.
Почему выводится undefined
, а не вываливается
ошибка в консоль, как это было в предыдущих
уроках? Потому что this
внутри функции,
вызываемой через setInterval
, указывает
на window
.
Это значит, что мы пытаемся прочитать свойство
value у объекта window
, вот так: window.value
,
а такого свойства в нем нет, и мы получаем
undefined
(не ошибку).
Поправим проблему введением self
:
elem.addEventListener('click', function() {
let self = this;
setInterval(function() {
console.log(self.value);
}, 1000);
});
Пусть дан такой код:
<input type="button" id="elem" value="1">
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
setInterval(function() {
this.value = Number(elem.value) + 1;
}, 1000);
});
Автор кода хотел, чтобы по нажатию на кнопку,
значение этой кнопки каждую секунду увеличивалось
на 1
. Однако, по нажатию на кнопку
вообще ничего не происходит. Исправьте ошибку
автора кода. Напишите текст, в котором вы
дадите объяснение автору кода, почему возникла
его ошибка.
Другие способы решить проблему
Конечно же, можно использовать все известные вам способы решения проблем с контекстом. Например, можно использовать стрелочную функцию:
elem.addEventListener('click', function() {
setInterval(() => console.log(this.value), 1000);
});
Исправьте проблему предыдущей задачи через стрелочную функцию.
Решение проблемы через замыкание
Можно также решить проблему с контекстом, используя замыкание:
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
function func(self) {
return function() {
console.log(self.value);
}
}
setInterval(func(this), 1000);
});
Можно вообще не использовать именованную функцию, а сделать вызов функции на месте. В этом случае мы получим более изящное, но менее понятное решение:
let elem = document.querySelector('#elem');
elem.addEventListener('click', function() {
setInterval((function(self) {
return function() {
console.log(self.value);
}
})(this), 1000);
});
Объясните принцип работы приведенного мною кода.