修复循环中的闭包

Fixing a closure within a loop

我正在阅读 David Flanagan 的 JavaScript:权威指南,我被困在以下示例中:

window.onload = function() {
    var elements = document.getElementsByClassName("reveal");
    for(var i = 0; i < elements.length; i++) {
        var elt = elements[i];
        var title = elt.getElementsByClassName("handle")[0];
        title.onclick = function() {
            if(elt.className == "reveal") elt.className = "revealed";
            else if(elt.className == "revealed") elt.className = "reveal;"
        }
    }
};

<div class="reveal">
    <h1 class="handle">Click Here to Reveal Hidden Text</h1>
    <p>This paragraph is hidden. It appears when you click on the title.</p>
</div>

问题是当前形式的代码可以工作,但是如果我用 reveal 的 class 添加多个 div,无论 h1 我点击的元素,只有最后一个会展开。我应该在代码中更改什么才能使其在单独的 div 上工作?

P.S。我确实知道由于问题的性质是循环内的闭包,所以经常会问这种问题,但是鉴于 SO 上类似问题的解决方案,我无法使我的代码正常工作。谢谢。

在JavaScript中,变量范围设置在函数级别。函数创建 closures 本质上定义了范围的边界。 子作用域可以访问定义在父作用域中的变量,但反之则不行。

当您调用 onclick 处理程序函数时,它必须查找 up 作用域链 以找到变量 elt 因为 elt 没有在当前范围内声明。

范围链中的下一级是您的 window.onload 处理函数(即闭包)。在此范围内,变量 elt 声明的,它的值由循环设置。 但请记住,当您单击 <h1> 并调用 onclick 处理程序时,循环已经终止。因此,elt 将始终是循环中设置的最后一个值。这就是为什么您总是将 elt 作为最后一个元素。

要解决此问题,您可以使用立即执行函数。立即执行的函数创建一个新的闭包(和范围),其中 elt 在本地声明。因为变量 elt 现在是在这个新的内部作用域中声明的,所以它不会被父作用域中的循环修改:

window.onload = function() {
    var elements = document.getElementsByClassName("reveal");
    for(var i = 0; i < elements.length; i++) {
        var elt = elements[i];
        var title = elt.getElementsByClassName("handle")[0];
        title.onclick = (function(elt) {
            return function() {
                if(elt.className == "reveal") elt.className = "revealed";
                else if(elt.className == "revealed") elt.className = "reveal;"
            };
        })(elt);
    }
};