Scope and closure (4): closure
March 31, 2018
⏳ 3 min read
Definition of closure
Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope. Closure lets the function continue to access the lexical scope it was defined in at author-time.
Closure in practice
function outer(){
var a = 1;
function inner(){
console.log(a);
}
return inner; // here it is returned as a value, not as console.log(a);
}
var test = outer(); // mark 1
test();inner() has an access to the variable a according to the rule of lexical scope. outer() returns the function inner itself as a value, not as a return value of the function.
Now test receives the value of inner function. and test is run. But now it’s executed outside of its lexical scope.
At mark 1, the outer() function becomes no more useful. However the variable inside, which is a, can still be referenced by inner() that is not yet executed.
This, without understanding, of course happens, but it’s just a detailed look at how javascript behaves.
Loops and closure
Why this does not work as intended
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}So this is how I understood things:
- The for loop does not recognize our intention, which is to output 1 2 3 4 5, one at a time per second. Instead, it goes just very fast from
i = 1toi = 5(and further toi = 6according to the condition when the loop is escaped). - The
timerfunction refers to the sameistill. And by the time the loop is escaped,setTimeoutfires and gives an output. - But the second argument
setTimeoutfunction were correctly given becausei*1000would have been1000,2000,3000, … and5000respectively. - And so when
setTimeoutfires, it will give:6 6 6 6 6 // one per each second
Solution
The solution is IIFE and another variable to avoid referring to the same address (variable)
FYI, IIFE receives its input in this way:
var input = "im testing";
(function(test){
console.log(test)}
)(input); // outputs "im testing"The argument (input) in the outer bracket is what the argument (test) in the inner bracket refers to.
Both of the following ways are the solutions (copied from YDKJS):
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}for (var i=1; i<=5; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})( i );
}Now, setTimeout is referring to a variable of changed value (1,2,3,4,5) for each time of iteration because it has a closure over brand new scope for each iteration.
Another easy solution
let declares a variable for each iteration, which makes it unnecessary to do all the dirty works we did above:
for (var i=1; i<=5; i++) {
let j = i; // yay, block-scope for closure!
setTimeout( function timer(){
console.log( j );
}, j*1000 );
}and
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}will just work.