Javascript臭名昭着的循环问题?

Zhu*_*Tao 216 javascript closures

我有以下代码片段.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码用于生成5个链接,并使用alert事件绑定每个链接以显示当前链接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);
    }
}
Run Code Online (Sandbox Code Playgroud)

以上两个片段均引自此处.正如作者的解释,似乎关闭使魔术.

但它是如何工作的以及闭包如何使其工作都超出了我的理解范围.为什么第一个不工作而第二个工作?任何人都可以详细解释这个魔法吗?

谢谢.

Chr*_*oph 105

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请记住,扩展DOM(参见`link.i = i;`)被视为[一种不好的做法](http://perfectionkills.com/whats-wrong-with-extending-the-dom/). (3认同)
  • 但是,使用数据属性或类似jQuery的.data()之类的东西可以完成@check_ca.这些通常解决了该文章中的问题(例如,数据是为用户保留的,因此未来的标准永远不会定义`data-something`属性). (2认同)
  • @PhilippLudwig我建议用`link.setAttribute("data-link-index",i)`替换`link.i = i`,用`alert(Number(this.getAttribute)`替换`alert(this.i)` (“数据链接索引”)))` (2认同)

Dan*_*wis 78

我喜欢为厚厚的人写简单的解释,因为我很厚,所以这里...

我们在页面上有5个div,每个div都有一个ID ... div1,div2,div3,div4,div5

jQuery可以做到这一点......

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

但真正解决问题(并慢慢建立起来)......

步骤1

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

第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
    )
}
Run Code Online (Sandbox Code Playgroud)

简单地理解替代性

如果你无法理解,那么这应该更容易理解并具有相同的效果......

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一个我仍然无法理解的好问题.当你说**"当功能被调用时设置了值"**你的意思是只有在div上点击div的每个值时才设置?它始终通过引用节省功能范围 (2认同)
  • 不幸的是,这不再起作用,警报根本不会显示,控制台中也没有任何内容。 (2认同)

Ima*_*ist 20

基本上,在第一个示例中,您将处理程序i内部onclick直接绑定到处理程序i外部onclick.因此,当处理程序i外部onclick发生更改时,处理程序i内部onclick也会发生更改.

在第二个例子中,而不是将其绑定到numonclick处理程序中,你传递给函数,然后绑定到numonclick处理程序.当你将它传递到的功能,价值i复制,没有绑定num.所以当i变化时,num保持不变.复制的原因是JavaScript中的函数是"闭包",这意味着一旦将某些内容传递给函数,它就会被"关闭"以进行外部修改.

  • 我已经为这个主题阅读了几个答案,试图解决原因.你最后一句话的后半部分终于打开了我的脑袋......谢谢你,谢谢你,谢谢你! (4认同)

nlo*_*gax 17

其他人已经解释了发生了什么,这是另一种解决方案.

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

基本上,穷人勒芒结合.

  • 使用'with'语句时要小心.它有一些性能问题.http://webcloud.se/log/JavaScript-and-the-with-statement/ http://p2p.wrox.com/content/articles/javascript-best-practices-performance-be-scope-aware http:/ /www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/ (6认同)
  • mm,我从未见过使用过该语句的解决方案,很好;) (2认同)

Zed*_*Zed 5

在第一个示例中,您只需将此函数绑定到onclick事件:

function() {alert(i);};
Run Code Online (Sandbox Code Playgroud)

这意味着在click事件中,js应该警告addlink函数i变量的值.由于for循环(),它的值将为5.

在第二个示例中,您将生成一个与另一个函数绑定的函数:

function (num) {
  return function () { alert(num); };
}
Run Code Online (Sandbox Code Playgroud)

这意味着:如果使用值调用,则返回一个警报输入值的函数.例如,呼叫function(3)将返回function() { alert(3) };.

在每次迭代时都使用值i调用此函数,因此您可以为每个链接创建单独的onclick函数.

关键在于,在第一个示例中,您的函数包含一个变量引用,而在第二个示例中,在外部函数的帮助下,您使用实际值替换了引用.这被称为闭包大致是因为你在函数中"包含"变量的当前值而不是保持对它的引用.