【问题标题】:Event handler scope in JavascriptJavascript 中的事件处理程序范围
【发布时间】:2012-05-19 00:44:31
【问题描述】:

这可能是一个简单的问题,但我无法找出最佳答案。

我在屏幕上有 10 个 <div> 元素。他们每个人都有一个click() 事件监听器:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
    <div id="element0">Click me! (0)</div>
    <div id="element1">Click me! (1)</div>
    <div id="element2">Click me! (2)</div>
    <div id="element3">Click me! (3)</div>
    <div id="element4">Click me! (4)</div>
    <div id="element5">Click me! (5)</div>
    <div id="element6">Click me! (6)</div>
    <div id="element7">Click me! (7)</div>
    <div id="element8">Click me! (8)</div>
    <div id="element9">Click me! (9)</div>
    <script type="text/javascript">
    for ( var i = 0; i < 10; i++ ) {
        var element = document.getElementById( "element" + i );
        element.onclick = function () {
            alert( "Element " + i );
        }
    }
    </script>
</body>
</html>

但每次我点击一个元素时,它都会显示“元素 10”!似乎所有这些事件处理程序都在为 i 使用相同的值。

我希望它显示“元素 N”,其中 N 是当前元素的编号。我不想从元素 id 中提取 N 。我也不想使用 jQuery 的 data() 方法来存储它。我相信这个问题必须有一个更简单的解决方案,但我找不到它。有人吗?

【问题讨论】:

  • i 是在全局范围内声明的,而不是在 onclick 函数内,因此当在 onclick 内访问时,它是从全局范围内读取的。它显示10,因为那是循环的最后一次迭代。

标签: javascript


【解决方案1】:

在所有点击处理程序共享的外部范围内只有一个变量i。您需要为每个闭包创建一个局部变量 i。这将起作用:

for ( var i = 0; i < 10; i++ ) {
    var element = document.getElementById( "element" + i );
    element.onclick = (function(i){
        // returns a new function to be used as an onclick handler
        return function () {
            alert( "Element " + i );
        }
    })(i); // Pass in the value of the outer scope i
}

查看this article 中的“臭名昭著的循环”问题(并阅读整篇文章)以获取更多信息:)

【讨论】:

  • 所以我为每个事件监听器创建了两个函数。难道没有更高效更干净的版本吗?
  • @AlexStack 这非常有效。您的循环中有一个匿名函数,它立即执行并为每个单击处理程序返回一个带有本地 i 变量的新函数。每个事件处理程序仍然只有一个函数。
  • 我知道,但是在我们的项目中,每隔几秒就会创建数千个这个函数。这就是我关心效率的原因。
  • @AlexStack 我认为这不是问题。我一直在 700 毫秒以下使用 onclick 处理程序和 mouseover 和 mouseout 处理程序以及一些样式生成 10000 个 div:jsfiddle.net/GVxSk/2
  • @AlexStack 当然,Jamund 的事件委托解决方案的速度大约是其两倍,持续时间不到 350 毫秒:jsfiddle.net/GVxSk/3
【解决方案2】:

因此,正如另一篇文章所说,您的问题是在循环内执行异步代码的性质。在你的情况下,虽然有一个更好的方法来解决这个问题,那就是使用事件委托。

document.body.onclick = function(e) {
  if (e.target.tagName.toLowerCase() === "div") {
    alert( "Element " + e.target.id );
  }
}

如果你有 jQuery,你可以这样做:

$(document.body).on("click", "div", function() {
  alert($(this).attr("id"));
});

有关更多信息,请查看这篇文章: http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/

显然 jQuery 和其他库也会自动处理这些内容。

【讨论】:

  • 谢谢,但正如我所说,我不想解析触发元素的 id 来获取 N。我也尝试了代码。似乎需要将this.id 替换为e.target.id 才能正常工作。
  • 感谢您的修复。事件委托使用的资源比为每个 div 分配点击事件要少得多,所以我仍然推荐它而不是其他方法。
  • 这是实际的 HTML 还是您试图解决更复杂的问题?
  • api.jquery.com/index 如果您只想知道被点击的索引,这里实际上可能会有所帮助。
  • 是的,它是一个更大项目的一部分,但很难描述,所以我提出了一个代码 sn-p 来准确演示我无法解决的部分。感谢您提供代码和链接。
【解决方案3】:

您可以从元素的id 属性中获取数字:

for ( var i = 0; i < 10; i++ ) {
    var element = document.getElementById( "element" + i );
    element.onclick = function () {

        var i = this.id.substr(7);
        alert( "Element " + i );
    }
}​

JsFiddle here.

【讨论】:

  • 有趣的解决方案。虽然正如我所说,我不想解析 id 来获取号码。这只是演示项目中更大的“范围问题”的示例。
猜你喜欢
  • 2011-10-23
  • 1970-01-01
  • 1970-01-01
  • 2018-08-18
  • 1970-01-01
  • 2011-07-05
  • 1970-01-01
  • 2022-11-19
  • 2019-12-14
相关资源
最近更新 更多