var funcs=[];
for(let i=0;i<3;i++){
funcs[i]=function(){
return i;
}
}
alert(funcs[1]);
alert(funcs[1]());
function (){
return i;
}
The second one like this
1
But I don't konw why the funcs[1] can executed not reporting an error 'i is undefined';
Because the function created within the loop closes over the lexical environment that's active when it was created. That lexical environment is (conceptually) an object, which contains locals defined within it (and some other things), including the
i
variable, in this case the one created for that specific iteration of the loop body (because of the very special wayfor
treatslet
declarations in its initializer). This is the concept of a "closure," one of the central technologies of JavaScript. Even when execution passes out of the scope a given lexical environment is associated with (a function returns, we move on to the next iteration of the loop, etc.), if anything still has a reference to that environment object, like all objects it lives on.Because of how
for
handleslet
in its initializer, each entry infuncs
gets its own lexical environment, and thus its own copy ofi
.When you call one of those functions, a new environment object is created with its "outer" environment set to the environment attached to the function. When you reference
i
within the function code, it looks first at the function's environment and, ifi
isn't found there, it looks to the outer environment — where it finds it (in this case) and uses it.In a comment you've said
Exactly right. With
var
,i
would be hoisted up to the environment object associated with the function thefor
loop is in (or the global one, if this is global code). So all of the functions createed in the loop share the samei
, which by the time you're calling them has the value3
.This is one of the essential differences between
let
/const
andvar
:let
andconst
have block scope, andfor
has special handling forlet
in its initializer.Let's follow through these various environment objects as they get created. Assuming this code:
When we call
example
, after theconst funcs = []
but before thefor
loop starts, the current environment object is the one created for the call toexample
, so we have something like this in memory (ignoring some details):Now, the
for
loop starts: A new per-iteration environment object is created put in place as the "current" one, with the previous one as its "outer" environment. Ani
variable is created within that new per-iteration environment object, and given the value0
:During the loop iteration, we create a function and store it in
funcs
. The function gets a reference to the current environment object, which it keeps as[[Environment]]
(this is an implementation thing, you won't actually find that if you look at the function, it's not accessible at the code level, just within the JavaScript engine):Now this is where the clever handling of
let
infor
initializers works (and in fact, the clever handling oflet
andconst
within afor
loop's body as well): A new environment object for the next iteration is created, and the value ofi
is copied from thei
for the previous iteration to thei
for the next iteration. So we have:Then the
i
in that new environment is incremented to1
, and a new function is created and stored infuncs
, giving us:Then at the end of that iteration, we do the whole thing again for the last iteration, and get:
When we call
funcs[1]()
, an environment for the call is created and its[[Outer]]
environment is set to the[[Environment]]
of the function. So just before wereturn i
in that function, we have (leaving out some details):When the function looks up
i
, it looks in the current environment object. Since it's not there, it looks to the[[Outer]]
object. It finds it there, with the value1
, so that's the value it uses.In contrast, if we use
var
thei
is hoisted to the environment object for the call toexample
(wherefuncs
is), so after the loop we have this instead (note thati
is no longer on the per-iteration environments):Which means when we call
funcs[1]()
, a new environment is created for it, its[[Outer]]
is set to the function's[[Environment]]
, and just beforereturn i
we have:So when the function looks up
i
, it doesn't find it in the current environment, and it doesn't find it in the first[[Outer]]
environment, but it does find it in the second[[Outer]]
environment, with the value3
, so that's the value it uses.