当使用匿名函数传递参数时,addEventListener会多次触发相同的处理程序。

19

当向匿名函数传递参数时,由于某种原因,每个元素的事件侦听器会触发两次。即,元素 el 上的单击事件将注册一次并因此触发一次。

el.addEventListener("click", handle, false);
el.addEventListener("click", handle, false);

但是如果我想将自己的参数传递给它,它会注册并触发两次。

el.addEventListener("click", function() { handle(event, myArgument); }, false);
el.addEventListener("click", function() { handle(event, myArgument); }, false);

问题是为什么,以及有什么解决方案?

我在其他地方搜索,似乎找不到解决方案或者无法理解为什么出现这个问题。我尝试了在如何将参数传递给addEventListener中传递的监听器函数中实施的解决方案,但它们没有帮助 -

我做了基本的匿名函数或闭包,然后是更高级的版本,即下面所示的代码,但仍然没用。

我不明白为什么不传递参数会导致元素事件注册一次,而传递参数会导致元素事件注册两次。

以下是代码:

<html>
    <head>
        <script type="text/javascript">
            var handle_2 = function(evt, type) {
                var test;
                switch (type) {
                    case "focus": 
                        console.log(evt.target.value);
                        break;
                    case "click":
                        console.log(evt.target.id + " was clicked");
                        break;
                    default: console.log("no type found");
                }
            };
        
            window.onload = function() {
                var textbox = document.getElementById("t1");
                var button = document.getElementById("btn");
                textbox.value = "456";
                button.value = "Press";

                var typeFocus = "focus", typeClick = "click";

                textbox.addEventListener("focus", (function(typeFocus) { return function(evt) { handle_2(evt, typeFocus); }})(typeFocus), false);
                button.addEventListener("click", (function(typeClick) { return function(evt) { handle_2(evt, typeClick); }})(typeClick), false);
                
                // Registers again for each element. Why?
                textbox.addEventListener("focus", (function(typeFocus) { return function(evt) { handle_2(evt, typeFocus); }})(typeFocus), false);
                button.addEventListener("click", (function(typeClick) { return function(evt) { handle_2(evt, typeClick); }})(typeClick), false);
            };
        </script>
    </head>
    <body>
        <div id="wrapper">
            <input id="t1" type="text" />
            <input id="btn" type="button" />
        </div>
    </body>
</html>

非常感谢您的帮助。


2
你为什么要调用 el.addEventListener 两次? - Felix Kling
1
这超出了讨论的范围。它与我正在处理的某些供应商代码有关。 - user717236
1
如果在同一元素上两次调用 addEventListener(),则监听器将运行两次。 - Barmar
4个回答

35

最简单的解决方案是只创建一次新的处理程序:

var newHandle = function(event) { handle(event, myArgument); };

el.addEventListener("click", newHandle, false);
el.addEventListener("click", newHandle, false);
如果在同一元素上使用完全相同的事件类型、处理程序和捕获值多次调用addEventListener,则处理程序仅会被注册一次。根据DOM规范:

...
5. 如果eventTarget的事件监听器列表不包含类型为listener类型、回调函数为listener的回调函数和捕获方式为listener的捕获方式的事件监听器,则将listener添加到eventTarget的事件监听器列表中。
...


非常感谢。是的,这个方法完美地解决了问题。我也可以使用这种方法删除监听器。我现在明白为什么之前它没有起作用了。再次感谢。 - user717236
@nim:你的事件处理程序 - Felix Kling
@FelixKling handler是一个函数还是什么? - nim
1
@user717236 你怎么移除监听器?只需要使用 el.removeEventListener("click", newHandle) 吗? - dingo_d
它对我有效,如何将其添加到普通的Angular组件中? - qleoz12
显示剩余2条评论

9

Well,,

el.addEventListener("click", handle, false);
el.addEventListener("click", handle, false);

向同一个函数“handle()”注册

el.addEventListener("click", function() { handle(event, myArgument); }, false);
el.addEventListener("click", function() { handle(event, myArgument); }, false);

注册了两个独特的匿名函数 "function() { handle(event, myArgument)"...,因此它会触发两次。

虽然我不完全理解为什么您想要注册两次,但解决方案是创建一个返回带参数函数的函数。

el.addEventListener("click", crateHandle(myArgument), false);

var createHandle = function(myArgument) {
  return function(event) {
    .... do something
  };
}

然而,这仍然没有解决火灾两次的问题。


嗯,因为引用不同?那么,我该如何向侦听器传递参数并确保它只注册一次?谢谢。 - user717236
2
如果调用el.addEventListener("click", crateHandle(myArgument), false);两次,您的解决方案将遇到相同的问题。 - Felix Kling
这与一个由供应商创建的代码有关,我需要进行修改。我正在为他们的几个本地函数添加事件监听器。假设我能以另一种方式做到这一点,以避免多个事件监听器,那就太好了。非常感谢。 - user717236
没问题。我很感激你的诚实和友善的帮助!在这种情况下,我该如何调用removeEventListener?由于我正在向事件侦听器传递参数,所以我想我需要确保在调用removeEventListener时引用相同。再次感谢。 - user717236
通常情况下,您需要保存对所创建函数的引用并将其删除。我猜您也可以将“myArgument”保存为全局变量,并通过句柄函数访问它。 - Stefan Svebeck
显示剩余2条评论

2

addEventListener可以注册多个监听器。

根据文档,它有三个参数,第三个参数是useCapture,与是否重复注册监听器无关。默认情况下,它被设置为false,因此将false作为第三个参数并不会改变太多。


1
True。但是如果您在此处阅读文档中的文本,会发现它会忽略重复注册的情况-- 如果在相同的EventTarget上使用相同参数注册多个相同的EventListeners,则会丢弃重复的实例。它们不会导致EventListener被调用两次,并且由于重复项被丢弃,因此无需使用removeEventListener方法手动删除它们。 - user717236
1
@user3907208:没错。在第一种情况下,您传递了相同的函数,因此忽略了第二个函数。在第二种情况下,您传递了两个不同的函数。 - Felix Kling
不同之处在于内存引用,我收集到了。那么,我怎样才能确保一个事件只会被注册一次并传递我的参数呢?谢谢。 - user717236
1
@user3907208:只需调用一次addEventListener - Felix Kling
@user3907208:那么你必须存储对新函数的引用:var handler2 = function() { handler(....); };,然后使用该函数。 - Felix Kling
抱歉,Felix。如果我能用另一种方式做到这一点,我会的。我只是一个必须对供应商的代码进行一些更改的信使,然后出现了这个问题...哦,存储一个引用。好的,那我会看看我能想出什么,我很重视你的意见!谢谢。 - user717236

0
 useEffect(()=>{
        document.addEventListener('keydown', someFunction); 
    }, []);

使用useEffect钩子,结尾的空数组将告诉React不要在加载后刷新组件。


5
useEffect钩子仅在React中可用,而不是JavaScript... - tcanbolat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接