Timers and context loss in JavaScript

When using timers in event handlers, we are faced with problems with loss of context. Let's look at an example.

Let us have an input:

<input id="elem" value="text">

Let an anonymous function be triggered by clicking on this input and a timer will start inside this function, outputting something to the console every second:

let elem = document.querySelector('#elem'); elem.addEventListener('click', function() { setInterval(function() { console.log('!!!'); // outputs something to the console }, 1000); });

As long as everything is working correctly. But now let's say we want to output value of our input to the console - a surprise awaits us: undefined will be output to the console:

elem.addEventListener('click', function() { setInterval(function() { console.log(this.value); // will output undefined }, 1000); });

The thing is that we get a function within a function: there is an external anonymous function that is called on click and an internal anonymous function that the timer starts. In the outer function, this points to an input, but not in the inner one. There is a loss of context.

Why is undefined displayed instead of throwing an error in the console, as it was in previous lessons? Because this inside a function called via setInterval points to window.

This means that we are trying to read the value property of the window object, like this: window.value, but there is no such property in it, and we get undefined (not an error).

Let's fix the problem by introducing self:

elem.addEventListener('click', function() { let self = this; setInterval(function() { console.log(self.value); }, 1000); });

Let this code be given:

<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); });

The author of the code wanted that when a button is pressed, the value of this button increases by 1 every second. However, when the button is pressed, nothing happens at all. Please correct the code author's mistake. Write a text in which you will explain to the author of the code why this error occurred.

Other ways to solve the problem

Of course, you can use all methods for solving context problems you know. For example, you can use the arrow function:

elem.addEventListener('click', function() { setInterval(() => console.log(this.value), 1000); });

Fix the problem of the previous task with an arrow function.

Solving the problem using closure

You can also solve the context problem by using closure:

let elem = document.querySelector('#elem'); elem.addEventListener('click', function() { function func(self) { return function() { console.log(self.value); } } setInterval(func(this), 1000); });

You can not use a named function at all, but perform calling a function in-place. In this case, we get a more elegant, but less clear solution:

let elem = document.querySelector('#elem'); elem.addEventListener('click', function() { setInterval((function(self) { return function() { console.log(self.value); } })(this), 1000); });

Explain how the code I've provided works.