问题
From You Don't Know JS:
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
gives
6
6
6
6
6
but using an IIFE like so
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
gives
1
2
3
4
5
My question: why doesn't
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
var j = i;
console.log( j );
}, i*1000 );
}
or
for (var i=1; i<=5; i++) {
function timer() {
var j = i;
console.log(j);
}
setTimeout(timer, i*1000 );
}
work like the IIFE example? It seems to me they both have a function
declaration with a new variable j
, wouldn't that create a new lexical scope with a specific setting for i
?
回答1:
The important part of the IIFE is that it runs right away; before i
changes, it reads its value and puts it in a new variable. The function reading i
in your other examples – function timer()
– does not run right away, and the value it puts in its new variable is the value of i
after it’s already changed.
Also, in ES6, you can just let i = …
instead of var i = …
and it’ll work fine without the IIFE or j
:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
because let
has block scope instead of function scope and variables declared in the initialization part of for
loops count as being half-inside the for
’s block.
回答2:
i
, being declared with var
, is hoisted. Variables don't automatically get their scopes bound to an inner function; unless the inner function explicitly has var i
or a paramter of i
(thus defining a new i
bound to the scope of the inner function), i
will continue to refer to the hoisted i
in the outer scope.
For example, you could do what you were thinking of like this, if you wanted:
for (var i=1; i<=5; i++) {
setTimeout( function timer(i){
console.log( i );
}, i*1000, i );
}
(The third argument to setTimeout
is what the function, the second argument, will be called with)
This means that timer
will be called with i
as it is during iteration, and the function will use a new i
, bound to the scope of the function, initialized via the parameter.
It's a pretty bad idea, though - better to use const
and let
, which have block scope rather than function scope, and better not to shadow outer variables.
回答3:
This sort of IIFE
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
is often written like
for (var i=1; i<=5; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})(i);
}
so, you can see that "captured" value is i
in this case
You can do the same without IIFE
for (var i=1; i<=5; i++) {
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
timer(i);
}
of course, this is equivalent of
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
for (var i=1; i<=5; i++) {
timer(i);
}
if using ES2015+, you can use let
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
Now, if you use a transpiler because you need to support ES5 (or whatever internet exploder supports) you'll see that the transpiled version is
var _loop = function _loop(i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
};
for (var i = 1; i <= 5; i++) {
_loop(i);
}
Which looks incredibly like the previous version of the code
来源:https://stackoverflow.com/questions/50615610/why-is-iife-needed-to-create-a-new-scope