Javascript闭包 - 变量与参数

And*_*lly 11 javascript closures

我正在尝试学习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);
};
Run Code Online (Sandbox Code Playgroud)

当我在我的文档中有三个链接时,点击任何链接显示"3",我想因为在最后一次循环运行后x增加到3.我在这篇优秀的介绍中读到,如果你多次运行外部函数,每次都会创建一个新的闭包.那么为什么每次调用外部函数时每个闭包都不会为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);
};
Run Code Online (Sandbox Code Playgroud)

现在,当您单击第一个链接时,您将获得0,而在第二个链接上则为1.

任何人都可以解释为什么会有这种差异?

干杯

Mat*_*ard 12

闭包不会在创建变量时捕获变量的值,而是变量本身.由多个闭包关闭的变量是共享的.这是有意的,它是在JavaScript中进行封装的好方法,例如:

var makeMutablePoint = function(x, y) {
  return {
    position: function() {
      return [x, y];
    },
    add: function(dx, dy) {
      x = x + dx;
      y = y + dy;
    }
  };
};
Run Code Online (Sandbox Code Playgroud)

这也是闭包在大多数其他语言中工作的方式(这是Python有时被称为没有适当的闭包的主要原因).

但是有一个方面是特定于JavaScript的,有时可能会让你失望(在这种情况下实际上似乎已经这样做了):变量总是在JavaScript中具有函数范围.例如,在您的第一个代码片段中,只有一个x变量,而有人可能期望将范围x限制为循环体(x每次迭代都有一个新变量).这是一种语言的怪癖,将来可能会通过引入let更细粒度的范围规则的关键字来改进.


aro*_*oth 3

我过去对这种同样的行为感到有点恼火。对我来说,这似乎是闭包实现中的一个错误。闭包应该包含“创建闭包时函数的词法环境(例如,可用变量及其值的集合) ”的快照(来源:维基百科;重点是我的)。显然,这并不完全是本例中发生的情况。

但很容易推断出幕后发生的事情。在您的第一个示例中,只有一个变量 实例x,并且当创建闭包时,JavaScript 运行时会在其中存储对创建闭包时x的当前值的引用,而不是存储当前值的副本。x因此,当循环递增时x,闭包中的“副本”似乎也会递增。

在第二种情况下,您将x其作为参数传递给函数,该函数x在传递给attachListener()函数时将 的当前值复制到新变量中。该副本的值永远不会更新(即,它与 解耦x,并且您不会在 内部修改它attachListener()),因此闭包按预期工作,因为它存储对副本的引用而不是对原始 的引用x

  • 您对闭包如何工作的猜测对于 JavaScript 等真正的函数式语言来说并不准确。闭包总是涉及对词法范围内的变量的*引用*,**而不是副本。**事实上,这就是该工具如此强大的原因。某些语言(Java)**确实**创建词法作用域的“死”副本,但由于这个原因,此类设施不应该被称为“闭包”,因为它们不是。 (6认同)
  • @Pointy:很好的评论:这就是为什么在Java中“闭包”变量必须用final关键字声明。 (2认同)