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 = 1
toi = 5
(and further toi = 6
according to the condition when the loop is escaped). - The
timer
function refers to the samei
still. And by the time the loop is escaped,setTimeout
fires and gives an output. - But the second argument
setTimeout
function were correctly given becausei*1000
would have been1000
,2000
,3000
, … and5000
respectively. - And so when
setTimeout
fires, 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.