Javascript闭包-变量与参数
我正在尝试学习Javascript闭包。我很难理解这样一个事实:当你在一个循环中创建几个闭包时,所有闭包只保存变量的最后一个状态。举个例子Javascript闭包-变量与参数,javascript,closures,Javascript,Closures,我正在尝试学习Javascript闭包。我很难理解这样一个事实:当你在一个循环中创建几个闭包时,所有闭包只保存变量的最后一个状态。举个例子 var links = document.getElementsByTagName('a'); for (var x=0; x<links.length; x++) attachListener(); function attachListener() { links[x].addEventListener('click', fun
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);
};
var links=document.getElementsByTagName('a');
对于(var x=0;x在第一个示例中,x
是附加事件侦听器的外部函数范围的一部分,因此增加到3
(outer scope)
x
attachListener
(no local variables)
在过去,我对同样的行为感到有些恼火。在我看来,这似乎是闭包实现中的一个错误。闭包应该包括“创建闭包时函数的词汇环境(例如,可用变量集及其值)”的快照(来源:Wikipedia;emphasis mine)。很明显,在这个例子中,情况并非如此
但是很容易推断出幕后发生了什么。在第一个示例中,变量x
只有一个实例,当创建闭包时,JavaScript运行时会在其中存储对x
的引用,而不是在创建闭包时当前值x
的副本因此,当循环增加x
时,闭包中的“副本”也会增加
在第二种情况下,将x
作为参数传递给函数,该函数在传递给attachListener()
函数时,将x
的当前值复制到一个新变量中。此副本的值永远不会更新(也就是说,它与x
解耦,您不需要在attachListener()
中修改它),因此闭包按预期工作,因为它存储对副本的引用,而不是对原始的x
闭包在创建时不捕获变量的值,而是捕获变量本身。由多个闭包关闭的变量是共享的。这是有意的,也是进行封装的好方法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
关键字加以改进。这是由于对闭包的误解而导致的一个常见错误(也称为)。下面是一个非常基本的示例:
var funcs = [];
for (var i = 0; i < 5; i++) {
funcs[i] = function() { return i; };
}
> funcs[0]();
5
> funcs[1]();
5
var funcs=[];
对于(变量i=0;i<5;i++){
funcs[i]=function(){return i;};
}
>funcs[0]();
5.
>funcs[1]();
5.
这里要理解的主要事情是闭包关闭(或记住,或“抓取”)对非局部(或自由)变量的引用。现在重点是引用,即闭包不抓取自由变量的值,而是抓取对其名称的引用
如果一个闭包会记住自由变量的值,那么我们就会有(错误的)预期行为funcs[0]()
返回0
,等等。如果一个闭包会记住自由变量的值,那么我们可以说闭包会在特定时刻对这些值进行“快照”
但这并不是关闭的作用
闭包记住对自由变量的引用,而不是它们的值。
在本例中,funcs[i]
记住对i
的引用。调用时,它查找全局变量i
的值,该值当前为5
真正帮助我理解这一点的是第12-15页,我在其中使用了一些摘录和上的定义。谢谢Marcel-Helen的文章,文章说闭包在外部函数退出时保存变量状态。因此在第一个示例中,我预计如果我在外部函数外定义并递增x,它将使用每次x的值不同。也许更准确的说法是,闭包会像整个脚本退出时一样记住变量的最新值?@并且-你是对的,闭包应该在创建闭包时记住当前执行状态的快照,并且一旦创建了快照,该快照的状态就不应该更改可能会发生变化。但无论出于何种原因,JavaScript中都不会发生这种情况。您对闭包如何工作的猜测对于像JavaScript这样的真正函数式语言来说并不准确。闭包总是涉及到词法范围内的变量引用,而不是副本。事实上,这就是使该功能如此强大的原因。某些语言(Java)确实创建了词法作用域的“死”副本,但由于这个原因,这些工具不应该被称为“闭包”,因为它们不是。@Pointy:很好的注释:这就是为什么在Java中“闭包”是“变量必须用final关键字声明。PHP允许您通过适当地将闭包指定为use()的参数来控制闭包是通过引用还是通过值来访问词法环境中的变量。更多信息,请阅读此处接受的答案:(奇怪,我没有找到用于use()的正式PHP文档!)谢谢Matthias,非常有用!
var makeMutablePoint = function(x, y) {
return {
position: function() {
return [x, y];
},
add: function(dx, dy) {
x = x + dx;
y = y + dy;
}
};
};
var funcs = [];
for (var i = 0; i < 5; i++) {
funcs[i] = function() { return i; };
}
> funcs[0]();
5
> funcs[1]();
5