Javascript臭名昭著的Loop问题?

2022-08-30 00:52:55

我有以下代码片段。

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);
    }
}

以上2个片段从这里引用。正如作者的解释似乎使闭合变得神奇。

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


答案 1

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

JavaScript的作用域是函数级的,而不是块级的,创建闭包只是意味着封闭的作用域被添加到封闭函数的词法环境中。

循环终止后,函数级变量 i 的值为 5,这就是内部函数“看到”的内容。

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

请记住,这种方法效率相当低,因为必须为每个链接创建两个新的函数对象。这是不必要的,因为如果您使用 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);
    }
}

答案 2

我们在页面上有5个div,每个div都有一个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);
    
}

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