JavaScript closures vs. anonymous functions
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
for (var i = 0; i < 10; i++) {
(function f() {
var i2 = i;
setTimeout(function g() {
console.log(i2);
}, 1000);
})();
}
In the above program there are two functions: f
and g
. 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 by f
. setTimeout
is not closed over by f
. console
is not closed over by f
. 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 of f
. setTimeout
. console
is not closed over by g
. i2
is closed over by g
. Thus the function g
is a closure for the free variable i2
(which is an upvalue for g
) when it's referenced from within setTimeout
.
Bad for you: Your friend is using a closure. The inner function is a closure.
Case 2: Your Program
for (var i = 0; i < 10; i++) {
setTimeout((function f(i2) {
return function g() {
console.log(i2);
};
})(i), 1000);
}
In the above program there are two functions: f
and g
. 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 by f
. 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 of f
. setTimeout
. console
is not closed over by g
. i2
is closed over by g
. Thus the function g
is a closure for the free variable i2
(which is an upvalue for g
) when it's referenced from within setTimeout
.
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();
function lexicalScope() {
var message = "This is the control. You should be able to see this message being alerted.";
regularFunction();
function regularFunction() {
alert(eval("message"));
}
}
According to the closure
definition:
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
You are using closure
if you define a function which use a variable which is defined outside of the function. (we call the variable a free variable ).
They all use closure
(even in the 1st example).
In a nutshell Javascript Closures allow a function to access a variable that is declared in a lexical-parent function .
Let's see a more detailed explanation. To understand closures it is important to understand how JavaScript scopes variables.
Scopes
In JavaScript scopes are defined with functions. Every function defines a new scope.
Consider the following example;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
calling f prints
hello
hello
2
Am I Accessible?
Let's now consider the case we have a function g
defined within another function f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
We will call f
the lexical parent of g
. As explained before we now have 2 scopes; the scope f
and the scope g
.
But one scope is "within" the other scope, so is the scope of the child function part of the scope of the parent function? What happens with the variables declared in the scope of the parent function; will I be able to access them from the scope of the child function? That's exactly where closures step in.
Closures
In JavaScript the function g
can not only access any variables declared in scope g
but also access any variables declared in the scope of the parent function f
.
Consider following;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
calling f prints
hello
undefined
Let's look at the line console.log(foo);
. At this point we are in scope g
and we try to access the variable foo
that is declared in scope f
. But as stated before we can access any variable declared in a lexical parent function which is the case here; g
is the lexical parent of f
. Therefore hello
is printed.
Let's now look at the line console.log(bar);
. At this point we are in scope f
and we try to access the variable bar
that is declared in scope g
. bar
is not declared in the current scope and the function g
is not the parent of f
, therefore bar
is undefined
Actually we can also access the variables declared in the scope of a lexical "grand parent" function. Therefore if there would be a function h
defined within the function g
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
then h
would be able to access all the variables declared in the scope of function h
, g
, and f
. This is done with closures . In JavaScript closures allows us to access any variable declared in the lexical parent function, in the lexical grand parent function, in the lexical grand-grand parent function, etc. This can be seen as a scope chain ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
until the last parent function that has no lexical parent.
The window object
Actually the chain doesn't stop at the last parent function. There is one more special scope; the global scope . Every variable not declared in a function is considered to be declared in the global scope. The global scope has two specialities;
window
object. Therefore there are exactly two ways of declaring a variable foo
in the global scope; either by not declaring it in a function or by setting the property foo
of the window object.
Both attempts uses closures
Now that you have read a more detailed explanation it may now be apparent that both solutions uses closures. But to be sure, let's make a proof.
Let's create a new Programming Language; JavaScript-No-Closure. As the name suggests, JavaScript-No-Closure is identical to JavaScript except it doesn't support Closures.
In other words;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Alright, let's see what happens with the first solution with JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
therefore this will print undefined
10 times in JavaScript-No-Closure.
Hence the first solution uses closure.
Let's look at the second solution;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
therefore this will print undefined
10 times in JavaScript-No-Closure.
Both solutions uses closures.
Edit: It is assumed that these 3 code snippets are not defined in the global scope. Otherwise the variables foo
and i
would be bind to the window
object and therefore accessible through the window
object in both JavaScript and JavaScript-No-Closure.
上一篇: 在React JSX中循环
下一篇: JavaScript关闭与匿名函数