我正在尝试学习Javascript闭包。我无法理解当你在循环中创建几个闭包时,所有闭包只保存变量的最后一个状态。用这个例子
var links = document.getElementsByTagName('a');
for (var x=0; x<links.length; x++) attachListener();
function attachListener() {
links[x].addEventListener('click', function(){
console.log(x);
}, false);
};
当我的文档中有三个链接时,点击任何链接显示“3”,我猜是因为在最后一次循环运行后x增加到3。我在这个excellent intro中读到,如果你多次运行外部函数,每次都会创建一个新的闭包。那么为什么每次调用外部函数时每个闭包都不会为x保存不同的值?
当您将x作为参数传递给外部函数时,它会按预期工作。
var links = document.getElementsByTagName('a');
for (x=0; x<links.length; x++) attachListener(x);
function attachListener(z) {
links[z].addEventListener('click', function(){
console.log(z);
}, false);
};
现在,当你点击第一个链接时,你得到0,第二个链接就是1。
任何人都可以解释为什么会有这种差异?
干杯
答案 0 :(得分:11)
在创建变量时,闭包不会捕获变量的值,而变量本身。由多个闭包关闭的变量是共享的。这是有意的,它是在JavaScript中进行封装的好方法,例如像这样:
var makeMutablePoint = function(x, y) {
return {
position: function() {
return [x, y];
},
add: function(dx, dy) {
x = x + dx;
y = y + dy;
}
};
};
这也是闭包在大多数其他语言中工作的方式(这是Python有时被称为而不是具有适当闭包的主要原因)。
有一个方面是特定于JavaScript的,有时可能会让你失望(实际上在这种情况下似乎已经这样做了):变量总是在JavaScript中具有函数范围。例如,在您的第一个代码段中,只有一个x
变量,而有人可能会认为x
的范围仅限于循环体(使用新的x
每次迭代)。这是一种语言的怪癖,将来可能会通过引入let
关键字以及更细粒度的范围规则来改进。
答案 1 :(得分:3)
在第一个示例中,x
是附加事件侦听器的外部函数范围的一部分,因此增加到3。
(outer scope) x attachListener (no local variables)
在第二个示例中,z
成为attachListener
范围的一部分,保留在闭包内。
(outer scope) x attachListener z
答案 2 :(得分:3)
过去,我对这种同样的行为感到有点恼火。对我来说,这似乎是闭包实现中的一个错误。闭包应该包括“函数的词汇环境(例如,可用变量及其值的集合)在创建闭包时的快照”(来源:维基百科;强调我的) 。显然,这不是这个例子中发生的事情。
但很容易推断出幕后发生的事情。在您的第一个示例中,只有一个变量x
的实例,并且在创建闭包时,JavaScript运行时在其中存储对x
的引用,而不是当前值的副本创建闭包时x
。因此,当循环递增x
时,闭包中的“副本”也会增加。
在第二种情况下,您将x
作为参数传递给函数,该函数在传递给x
函数时将attachListener()
的当前值复制到新变量中。永远不会更新此副本的值(即它与x
分离,并且您不在attachListener()
内部对其进行修改),因此闭包按预期工作,因为它存储对该副本的引用复制而不是原始x
。
答案 3 :(得分:0)
这是一个常见的错误(也称为Javascript's infamous loop problem),由于对闭包的误解造成的。这是一个非常基本的例子:
var funcs = [];
for (var i = 0; i < 5; i++) {
funcs[i] = function() { return i; };
}
> funcs[0]();
5
> funcs[1]();
5
这里要理解的主要是闭包关闭(或记住或“抓取”)对非本地(或自由)变量的引用。现在重点是 references ,即闭包不会获取自由变量的值,但会抓取对其名称的引用。
如果一个闭包会记住自由变量的值,那么我们就会得到funcs[0]()
返回0
的(错误的)预期行为,等等。如果一个闭包会记住自由变量的值那么我们可以说封闭在那个特定的时刻对这些值进行“快照”。
但这不是关闭所做的。
Closures会记住对自由变量的引用,而不是它们的值。
在示例中,funcs[i]
会记住对i
的引用。调用时,它会查找全局变量i
的值,该值当前为5.
真正帮助我理解这一点的是this lecture第12-15页,我在其中使用了一些摘录和Wikipedia上的定义。