有人能解释一下js变量吊装和闭包的堆栈执行吗



在阅读另一个主题时,我遇到了以下代码:

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。正如作者所解释的,这里的问题是,xvar在函数开始时被提升,因此在循环中使用时不会保持其值。我不明白的是,为什么c在第二个console.log上被分配给x三次?有人能解释一下吗?

事实上,这种情况很容易解释,而且它发生在许多编程语言中,而不仅仅是JavaScript。

让我们首先取输出序列a,b,c,c,c,c,并丢弃前三个元素a,b,c,因为这是在第一个for循环中执行函数f()时打印的内容。

因此,我们只剩下序列c,c,c,当我们迭代fs数组时,它会被打印出来。没有打印a,b,c的原因是,第一个for循环中的x参数是由f()闭包引用捕获的(尽管不确定定义是否有效)(记住,JavaScript中的每个函数都是某种闭包)。

现在,由于console.log(x)表达式是在调用f()时计算的,因此捕获的x参数的当前值将作为参数传递。虽然代码可以正常工作,但正如预期的那样,当您在第一个for循环中调用f()时,x是刚刚从arr[i]分配的,因此您得到了a,b,c。但是,当您退出循环时,x(即使它对外部作用域不可用)仍然由f()捕获,但在第一个循环之后,它有一个值c(它在上一次迭代中得到的值),这就是当您第二次迭代函数时得到的值。

事实上,这可能是许多令人困惑的错误的来源,因此一些语言可以检测到这一点并通知开发人员(例如,C#编译器在看到这样的构造时,会产生以下警告:Access to modified closure)。

要解决这个问题,您只需要在第一个for循环中捕获x的当前值

for (var i in arr) {
var x = arr[i];
var f = (function(x) {
return function() { console.log(x) };
})(x);
f();
fs.push(f);
}

在这里,我们使用IIFE(立即调用函数表达式)来捕获x的当前值。

希望这能有所帮助。

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的当前值。

最新更新