我有下面的代码片段。

function addLinks () {
    for (var i=0, link; i<5; i++) {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function () {
            alert(i);
        };
        document.body.appendChild(link);
    }
}

上面的代码用于生成5个链接,并将每个链接与一个警报事件绑定,以显示当前链接id。但是不管用。当你点击生成的链接时,它们都显示“链接5”。

但是下面的代码片段符合我们的期望。

function addLinks () {
    for (var i=0, link; i<5; i++) {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function (num) {
            return function () {
                alert(num);
            };
        }(i);
        document.body.appendChild(link);
    }
}

以上两段话摘自此处。正如作者的解释所示,封闭产生了魔力。

但它是如何起作用的,以及封闭是如何起作用的,都超出了我的理解范围。为什么第一个不行,第二个可以?谁能详细解释一下这个魔术吗?


在第一个例子中,你简单地将这个函数绑定到onclick事件:

function() {alert(i);};

这意味着在点击事件中,js应该提醒addlink函数i变量的值。由于for循环(),它的值将为5。

在第二个例子中,生成一个函数与另一个函数绑定:

function (num) {
  return function () { alert(num); };
}

这意味着:如果用值调用,则返回给我一个函数,该函数将提醒输入值。例如,调用function(3)将返回function() {alert(3)};。

在每次迭代时调用这个函数,值为i,这样就为每个链接创建了单独的onclick函数。

重点是,在第一个示例中,函数包含变量引用,而在第二个示例中,借助外部函数,您将引用替换为实际值。这被称为闭包,因为您将变量的当前值“封闭”在函数中,而不是保留对它的引用。


引用我自己对第一个例子的解释:

JavaScript的作用域是函数级的,而不是块级的,创建闭包只是意味着封闭作用域被添加到封闭函数的词法环境中。 循环结束后,函数级变量i的值为5,这就是内部函数“看到的”。

在第二个例子中,对于每个迭代步骤,外部函数文字将计算为一个具有自己的作用域和局部变量num的新函数对象,其值被设置为i的当前值。由于num从未被修改,它将在闭包的生命周期内保持不变:下一个迭代步骤不会覆盖旧值,因为函数对象是独立的。

请记住,这种方法效率相当低,因为必须为每个链接创建两个新函数对象。这是不必要的,因为如果你使用DOM节点来存储信息,它们可以很容易地共享:

function linkListener() {
    alert(this.i);
}

function addLinks () {
    for(var i = 0; i < 5; ++i) {
        var link = document.createElement('a');
        link.appendChild(document.createTextNode('Link ' + i));
        link.i = i;
        link.onclick = linkListener;
        document.body.appendChild(link);
    }
}

基本上,在第一个例子中,你将onclick处理程序内部的i直接绑定到onclick处理程序外部的i。当onclick处理程序外的i改变时,onclick处理程序内的i也改变。

在第二个示例中,不是将其绑定到onclick处理程序中的num,而是将其传递到一个函数中,然后该函数将其绑定到onclick处理程序中的num。当你把它传递给函数时,i的值被复制,而不是绑定到num,所以当i改变时,num保持不变。复制发生的原因是JavaScript中的函数是“闭包”,这意味着一旦有东西被传递到函数中,它就会被“关闭”以供外部修改。


其他人已经解释了发生了什么,这里有一个替代解决方案。

function addLinks () {
  for (var i = 0, link; i < 5; i++) {
    link = document.createElement("a");
    link.innerHTML = "Link " + i;

    with ({ n: i }) {
      link.onclick = function() {
        alert(n);
      };
    }
    document.body.appendChild(link);
  }
}

基本上是穷人让捆绑。


我们在页面上有5个潜水员,每个人都有一个ID…Div1, div2, div3, div4, div5

jQuery可以做到这一点…

for (var i=1; i<=5; i++) {
    $("#div" + i).click ( function() { alert ($(this).index()) } )
}

但是真正解决这个问题(并且慢慢地建立这个问题)……

步骤1

for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        // TODO: Write function to handle click event
    )
}

步骤2

for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        function(num) {
            // A functions variable values are set WHEN THE FUNCTION IS CALLED!
            // PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
            // Now the click event is expecting a function as a handler so return it
            return function() { alert (num) }
        }(i) // We call the function here, passing in i
    )
}

简单易懂的选择

如果你不能理解这个,那么这个应该更容易理解,并且具有相同的效果……

for (var i=1; i<=5; i++) {

    function clickHandler(num) {    
        $("#div" + i).click (
            function() { alert (num) }
        )
    }
    clickHandler(i);
    
}

如果您记得函数变量值是在调用函数时设置的,那么这应该很容易理解(但这使用了与前面完全相同的思维过程)