A friend of mine and I are currently discussing what is a closure in JS and what isn't. We just want to make sure we really understand it correctly.
Let's take this example. We have a counting loop and want to print the counter variable on the console delayed. Therefore we use setTimeout
and closures to capture the value of the counter variable to make sure that it will not print N times the value N.
The wrong solution without closures or anything near to closures would be:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
which will of course print 10 times the value of i
after the loop, namely 10.
So his attempt was:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
printing 0 to 9 as expected.
I told him that he isn't using a closure to capture i
, but he insists that he is. I proved that he doesn't use closures by putting the for loop body within another setTimeout
(passing his anonymous function to setTimeout
), printing 10 times 10 again. The same applies if I store his function in a var
and execute it after the loop, also printing 10 times 10. So my argument is that he doesn't really capture the value of i
, making his version not a closure.
My attempt was:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
So I capture i
(named i2
within the closure), but now I return another function and pass this around. In my case, the function passed to setTimeout really captures i
.
Now who is using closures and who isn't?
Note that both solutions print 0 to 9 on the console delayed, so they solve the original problem, but we want to understand which of those two solutions uses closures to accomplish this.
Editor's Note: All functions in JavaScript are closures as explained in this post. However we are only interested in identifying a subset of these functions which are interesting from a theoretical point of view. Henceforth any reference to the word closure will refer to this subset of functions unless otherwise stated.
A simple explanation for closures:
Now let's use this to figure out who uses closures and who doesn't (for the sake of explanation I have named the functions):
Case 1: Your Friend's Program
In the above program there are two functions:
f
andg
. Let's see if they are closures:For
f
:i2
is a local variable.i
is a free variable.setTimeout
is a free variable.g
is a local variable.console
is a free variable.i
is bound to the global scope.setTimeout
is bound to the global scope.console
is bound to the global scope.i
is not closed over byf
.setTimeout
is not closed over byf
.console
is not closed over byf
.Thus the function
f
is not a closure.For
g
:console
is a free variable.i2
is a free variable.console
is bound to the global scope.i2
is bound to the scope off
.setTimeout
.console
is not closed over byg
.i2
is closed over byg
.Thus the function
g
is a closure for the free variablei2
(which is an upvalue forg
) when it's referenced from withinsetTimeout
.Bad for you: Your friend is using a closure. The inner function is a closure.
Case 2: Your Program
In the above program there are two functions:
f
andg
. Let's see if they are closures:For
f
:i2
is a local variable.g
is a local variable.console
is a free variable.console
is bound to the global scope.console
is not closed over byf
.Thus the function
f
is not a closure.For
g
:console
is a free variable.i2
is a free variable.console
is bound to the global scope.i2
is bound to the scope off
.setTimeout
.console
is not closed over byg
.i2
is closed over byg
.Thus the function
g
is a closure for the free variablei2
(which is an upvalue forg
) when it's referenced from withinsetTimeout
.Good for you: You are using a closure. The inner function is a closure.
So both you and your friend are using closures. Stop arguing. I hope I cleared the concept of closures and how to identify them for the both of you.
Edit: A simple explanation as to why are all functions closures (credits @Peter):
First let's consider the following program (it's the control):
lexicalScope
andregularFunction
aren't closures from the above definition.message
to be alerted becauseregularFunction
is not a closure (i.e. it has access to all the variables in its parent scope - includingmessage
).message
is indeed alerted.Next let's consider the following program (it's the alternative):
closureFunction
is a closure from the above definition.message
not to be alerted becauseclosureFunction
is a closure (i.e. it only has access to all its non-local variables at the time the function is created (see this answer) - this does not includemessage
).message
is actually being alerted.What do we infer from this?
Consider the following. This creates and recreates a function
f
that closes oni
, but different ones!:while the following closes on "a" function "itself"
( themselves! the snippet after this uses a single referent
f
)or to be more explicit:
NB. the last definition of
f
isfunction(){ console.log(9) }
before0
is printed.Caveat! The closure concept can be a coercive distraction from the essence of elementary programming:
x-refs.:
How do JavaScript closures work?
Javascript Closures Explanation
Does a (JS) Closure Require a Function Inside a Function
How to understand closures in Javascript?
Javascript local and global variable confusion
You and your friend both use closures:
In your friend's code function
function(){ console.log(i2); }
defined inside closure of anonymous functionfunction(){ var i2 = i; ...
and can read/write local variablei2
.In your code function
function(){ console.log(i2); }
defined inside closure of functionfunction(i2){ return ...
and can read/write local valuablei2
(declared in this case as a parameter).In both cases function
function(){ console.log(i2); }
then passed intosetTimeout
.Another equivalent (but with less memory utilization) is:
Let's look at both ways:
Declares and immediately executes an anonymous function that runs
setTimeout()
within its own context. The current value ofi
is preserved by making a copy intoi2
first; it works because of the immediate execution.Declares an execution context for the inner function whereby the current value of
i
is preserved intoi2
; this approach also uses immediate execution to preserve the value.Important
It should be mentioned that the run semantics are NOT the same between both approaches; your inner function gets passed to
setTimeout()
whereas his inner function callssetTimeout()
itself.Wrapping both codes inside another
setTimeout()
doesn't prove that only the second approach uses closures, there's just not the same thing to begin with.Conclusion
Both methods use closures, so it comes down to personal taste; the second approach is easier to "move" around or generalize.
You are both using closures.
I 'm going with the Wikipedia definition here:
Your friend's attempt clearly uses the variable
i
, which is non-local, by taking its value and making a copy to store into the locali2
.Your own attempt passes
i
(which at the call site is in scope) to an anonymous function as an argument. This is not a closure so far, but then that function returns another function that references the samei2
. Since inside the inner anonymous functioni2
is not a local, this creates a closure.I've never been happy with the way anybody explains this.
The key to understanding closures is to understand what JS would be like without closures.
Without closures, this would throw an error
Once outerFunc has returned in an imaginary closure-disabled version of JavaScript, the reference to outerVar would be garbage collected and gone leaving nothing there for the inner func to reference.
Closures are essentially the special rules that kick in and make it possible for those vars to exist when an inner function references an outer function's variables. With closures the vars referenced are maintained even after the outer function is done or 'closed' if that helps you remember the point.
Even with closures, the life cycle of local vars in a function with no inner funcs that reference its locals works the same as it would in a closure-less version. When the function is finished, the locals get garbage collected.
Once you have a reference in an inner func to an outer var, however it's like a doorjamb gets put in the way of garbage collection for those referenced vars.
A perhaps more accurate way to look at closures, is that the inner function basically uses the inner scope as its own scope foudnation.
But the context referenced is in fact, persistent, not like a snapshot. Repeatedly firing a returned inner function that keeps incrementing and logging an outer function's local var will keep alerting higher values.