在阅读其他主题时,我遇到了这段代码:
function loadme() {
var arr = ["a", "b", "c"];
var fs = [];
for (var i in arr) {
var x = arr[i];
var f = function() { console.log(x) };
f();
fs.push(f);
}
for (var j in fs) {
fs[j]();
}
}
这将输出:a,b,c,c,c,c。作者解释的问题是,x var在函数开始时被提升,因此在循环中使用它时不会保留它的值。我不明白为什么c在第二个console.log上分配给x 3次?有人可以解释一下吗?
答案 0 :(得分:4)
事实上,这种情况很容易解释,它出现在许多编程语言中,而不仅仅是JavaScript。
让我们首先选择输出序列a,b,c,c,c,c
并丢弃前三个元素 - a,b,c
,因为这是在第一个f()
内执行函数for
时打印的内容环。
因此,我们留下了序列c,c,c
,当我们遍历fs
数组时,它会被打印出来。您没有打印a,b,c
的原因是因为您的第一个x
循环中的for
参数是通过引用捕获的(不确定是否你的f()
闭包定义是有效的(请记住,JavaScript中的每个函数都是某种闭包)。
现在,由于在调用console.log(x)
时会计算f()
表达式,因此捕获的x
参数的当前值将作为参数传递。虽然代码可以正常工作,但是当您在第一个f()
循环中调用for
时,x
就是从arr[i]
分配的内容,因此您可以获得a,b,c
。但是当你退出循环时,x
(即使它对外部范围不可用)仍然被f()
捕获,但在第一个循环之后它有一个值c
(那个它在最后一次迭代中得到了),这是第二次迭代函数时得到的评估结果。
事实上,这可能是许多令人困惑的错误的来源,因此一些语言可以检测到这一点并通知开发人员(例如,C#编译器,当它看到这样的结构时,会产生以下警告:{{1 }})。
要解决此问题,您只需要在第一个Access to modified closure
循环中捕获 x
的当前值:
for
这里我们使用IIFE(立即调用的函数表达式)来捕获for (var i in arr) {
var x = arr[i];
var f = (function(x) {
return function() { console.log(x) };
})(x);
f();
fs.push(f);
}
的当前值。
希望这有帮助。
答案 1 :(得分:3)
var x = arr[i];
var f = function() { console.log(x) };
在这两行中,console.log(x)
认为只需要打印x
。所以,这三个函数都是以这种方式创建的。因此,当您立即执行这些函数时,x
的值在每次迭代中都是不同的。但是当循环结束时,变量x
会保留它所持有的最后一个值,并且当执行动态创建的函数时,它们只会打印x
的值c
。
因此,为了解决这个问题,您必须为每个动态创建的函数拥有自己的x
副本。通常,我们使用函数参数保留变量的当前状态,该变量在循环中发生变化,如此
var f = function(x) { return function() { console.log(x) } }(x);
现在,我们正在创建一个函数并立即执行它,它返回另一个函数,返回的函数实际上打印了x
的值。
function(x) {
return function() {
console.log(x)
}
}(x);
我们将x
的值作为参数传递给包装函数,现在保留x
的当前值。