在for循环中初始化的变量的作用域规则 [英] Scoping rules for variables initialized in a for loop
问题描述
<我在我的一个项目中玩setTimeout以限制向DOM添加元素(因此UI在页面加载期间不会冻结)。但是,我遇到了一些让我感到困惑的事情。鉴于此代码:
I'm playing around with setTimeout in a project of mine in order to throttle the adding of elements to the DOM (so UI won't freeze during page loading). However, I've encountered something a bit puzzling to me. Given this code:
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout(function() {
console.log("in timeout i is: " + i + " j is: " + j);
}, i * 1000);
}
我得到以下输出:
i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
超时中 i
的值为5是显而易见的,因为我在for循环初始化范围内。但是,对于所有超时输出, j
的结果是14?我原以为 j
会在超时中输出10,11,12,13,14,因为它在循环中作用域。我怎么能实现这个结果?
That the value of i
in the timeout is 5 is obvious since i is scoped in the for loop initialization. However, how come j
is 14 for all timeout outputs? I would have thought that j
would have output 10, 11, 12, 13, 14 in the timeout since it is scoped within the loop. How could I achieve that result?
推荐答案
那是因为,在JavaScript中, var
具有功能范围。
That is because, in JavaScript, var
has function scope.
var
声明将被提升至当前执行上下文的顶部。也就是说,如果它在函数内部, var
将是函数执行上下文中的作用域,否则是程序(全局)执行上下文。
var
declarations will be hoisted up to the top of the current execution context. That is, if it is inside of a function, the var
will be the scoped inside the function's execution context, otherwise the program (global) execution context.
ECMAScript 2015(又名ES6)介绍 让
允许你创建块范围变量,但由于它没有被广泛支持,我只是留下链接以供参考。
ECMAScript 2015 (a.k.a. ES6) introduces let
which lets you create block scope vars, but as it is not widely supported I'll just leave the link for reference.
一种解决方法,仍然使用 var
并让它在循环中限定,是创建一个新的执行上下文,知道为关闭:
An workaround, to still use var
and have it "scoped" inside the loop, is to create a new execution context, also know as closure:
function callbackFactory(i, j) {
// Now `i` and `j` are scoped inside each `callbackFactory` execution context.
return function() { // This returned function will be used by the `setTimeout`.
// Lexical scope (scope chain) will seek up the closest `i` and `j` in parent
// scopes, that being of the `callbackFactory`'s scope in which this returned
// function has been initialized.
console.log("in timeout i is: " + i + " j is: " + j);
};
}
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout( callbackFactory(i, j), i * 1000);
}
因为我确定了 i
和 j
在回调范围内,它们将在 setTimeout
内返回与它们时相同的值传递给 callbackFactory
。
As I scoped both i
and j
inside the callback scope, they will return the same values inside the setTimeout
than they had when they were passed to callbackFactory
.
参见 现场演示 。
另一种做同样事情的方法是创建内的$ c $http://benalman.com/news/2010/11/immediately-invoked-function-expression/ =nofollow noreferrer> IIFE c>循环。这通常更容易阅读,但JS(H | L)int会对你大喊大叫。 ;)
这是因为在循环中创建函数被认为对性能不利。
Another way to do the same thing is to create an IIFE inside the for
loop. This is usually simpler to read but JS(H|L)int will yell at you. ;)
This is because creating functions inside a loop is considered bad for performance.
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
(function(i, j) { // new execution context created for each iteration
setTimeout(function() {
console.log("in timeout i is: " + i + " j is: " + j);
}, i * 1000);
}(i, j)); // the variables inside the `for` are passed to the IIFE
}
上面我在每次迭代中,在中为
创建了一个新的执行上下文。 (演示)
Above I've created a new execution context inside the for
in each iteration. (Demo)
混合第一个使用上述IIFE的方法( callbackFactory
),我们甚至可以做出第3个选项:
Mixing the first approach (callbackFactory
) with the IIFE above, we could even make a 3rd option:
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout(function(i, j) {
return function() {
console.log("in timeout i is: " + i + " j is: " + j);
};
}(i, j), i * 1000);
}
这只是使用IIFE代替 callbackFactory
函数。这似乎不容易阅读,仍然在中为
循环创建函数,这对性能有害,但只是注意到这也是可能的并且工作。
This is simply using an IIFE in the place of the callbackFactory
function. This doesn't seem very easy to read and still creates functions inside the for
loop which is bad for performance, but just noting that this is also possible and works.
这3种方法在野生。 =]
These 3 approaches are very commonly seen in the wild. =]
哦,差点忘了回答主要问题。只需将 callbackFactory
放在与 for
循环相同的范围内,然后代替确定的范围我在
里面,让范围链寻找外部范围的 i
:
Oh, almost forgot to answer the main question. Just put the callbackFactory
in the same scope as the for
loop, then instead of scoping the i
inside of it, let the scope chain seek the i
of the outer scope:
(function() {
var i, j;
function callbackFactory(j) {
// the `j` inside this execution context enters it as a formal parameter,
// shadowing the outer `j`. That is, it is independent from the outer `j`.
// You could name the parameter as "k" and use "k" when logging, for example.
return function() {
// Scope chain will seek the closest `j` in parent scopes, that being
// the one from the callbackFactory's scope in which this returned
// function has been initialized.
// It will also seek up the "closest" `i`,
// which is scoped inside the outer wrapper IIFE.
console.log("in timeout i is: " + i + " j is: " + j);
};
}
for(i = 0; i < 5; i++) {
j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout( callbackFactory(j), i * 1000);
}
}());
/* Yields:
i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 10
in timeout i is: 5 j is: 11
in timeout i is: 5 j is: 12
in timeout i is: 5 j is: 13
in timeout i is: 5 j is: 14 */
请注意,我已移动 i
和 j
仅为了可读性而向范围顶部声明。它与的具有相同的效果,这将由解释器提升。
Note that I've moved the i
and j
declarations to the top of the scope solely for readability. It has the same effect as for (var i = [...]
, which would be hoisted up by the interpreter.
这篇关于在for循环中初始化的变量的作用域规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!