多个 addEventListener 如何在 JavaScript 中工作?

Posted

技术标签:

【中文标题】多个 addEventListener 如何在 JavaScript 中工作?【英文标题】:How do multiple addEventListener work in JavaScript? 【发布时间】:2013-04-22 19:45:46 【问题描述】:

一个文档中有2个脚本

// my_script.js goes first
document.onclick = function() 
    alert("document clicked");
;

// other_script.js comes after
// this overrides the onclick of my script,
// and alert will NOT be fired
document.onclick = function() 
    return false;
;

为了确保我的点击事件不会被其他脚本覆盖,我切换到addEventListener

// my_script.js goes first
document.addEventListener("click", function() 
    alert("document clicked");
, false);

// other_script.js comes after
document.addEventListener("click", function() 
    return false;
, false);

现在我有另一个问题。既然第二个代码中的return false是在alert之后定义的,为什么不阻止alert的调用呢?

如果我希望我的脚本完全控制点击事件(比如始终返回 false,而忽略其他脚本中定义的事件)怎么办?

【问题讨论】:

【参考方案1】:

如果我希望我的脚本完全控制点击事件(比如始终返回 false,而忽略其他脚本中定义的事件)怎么办?

如果您可以先注册您的处理程序,在他们注册之前,您可以这样做,前提是您使用的浏览器正确实现了 DOM3 事件(除非它是 IE8 或更早版本,否则它可能会这样做)。

这里(至少)涉及四件事:

    防止默认。

    停止传播到祖先元素。

    停止调用 same 元素上的其他处理程序。

    处理程序的调用顺序。

按顺序:

1。防止默认

这就是来自 DOM0 处理程序的 return false 所做的。 (详情:The Story on Return False。)DOM2 和 DOM3 中的等价物是preventDefault

document.addEventListener("click", function(e) 
    e.preventDefault();
, false);

防止默认值可能与您正在做的事情无关,但由于您在 DOM0 处理程序中使用了return false,这会阻止默认值,为了完整起见,我将其包含在此处。

2。停止传播到祖先元素

DOM0 处理程序无法做到这一点。 DOM2 的,通过stopPropagation:

document.addEventListener("click", function(e) 
    e.stopPropagation();
, false);

stopPropagation 不会阻止调用同一元素上的其他处理程序。来自the spec:

stopPropagation 方法用于防止在事件流期间进一步传播事件。如果任何EventListener 调用此方法,则事件将停止在树中传播。 事件将在事件流停止之前完成向当前EventTarget 上的所有侦听器的调度。

(我的重点。)

3。停止调用 same 元素上的其他处理程序

当然,DOM0 不会出现这种情况,因为对于同一元素上的同一事件,不可能有 其他处理程序。 :-)

据我所知,在 DOM2 中没有办法做到这一点,但 DOM3 给了我们stopImmediatePropagation

document.addEventListener("click", function(e) 
    e.stopImmediatePropagation();
, false);

一些库为通过库连接的处理程序提供此功能(甚至在 IE8 等非 DOM3 系统上),见下文。

4。调用处理程序的顺序

同样,与 DOM0 无关,因为不可能有其他处理程序。

在 DOM2 中,规范明确说,附加到元素的处理程序被调用的顺序并不能保证;但是 DOM3 改变了这一点,表示处理程序按照它们注册的顺序被调用。

首先,来自 DOM2 Section 1.2.1:

尽管EventTarget 上的所有EventListeners 都保证会被该EventTarget 接收到的任何事件触发,但没有指定它们接收事件的顺序相对于另一个EventListenersEventTarget

但这已被 DOM3 Section 3.1 取代:

接下来,实现必须确定当前目标的候选事件侦听器。这必须是按注册顺序在当前目标上注册的所有事件侦听器的列表。

(我的重点。)

如果您将事件与库挂钩,一些库会保证顺序。

还值得注意的是,在 Microsoft 的 DOM2 前身(例如,attachEvent)中,它与 DOM3 的顺序相反:处理程序以 reverse 的注册顺序调用。


所以把#3 和#4 放在一起,如果你可以先注册你的处理程序,它会先被调用,你可以使用stopImmediatePropagation 来防止其他处理程序被调用。前提是浏览器正确实现了 DOM3。


所有这些(包括 IE8 和更早版本甚至不实现 DOM2 事件,更不用说 DOM3)是人们使用 jQuery 之类的库的原因之一,其中一些确实保证了顺序(只要一切都在连接他们的处理程序通过有问题的库),并提供了阻止同一元素上的其他处理程序被调用的方法。 (例如,对于 jQuery,顺序是它们附加的顺序,您可以使用 stopImmediatePropagation 来停止对其他处理程序的调用。但我不想在这里出售 jQuery,只是解释一些库提供更多功能比基本的 DOM 东西。)

【讨论】:

好吧,我在发帖之前实际上已经尝试过(preventDefault,还有stopPropagation)。警报仍然会被调用。 我将最后一段代码 (addEventListener ... return false) 封装在一个 setTimeout (3000) 中,这应该确保它在 alert() 之后被添加,但是仍然会调用 alert。 @user1643156:同样:不能保证调用处理程序的顺序。另外,据我所知,没有办法通过 DOM2 停止调用同一元素上的其他处理程序(stopPropagation 停止将事件转到 ancestor 元素)。 我将警报更改为 console.log。我一直在点击页面,试图得到一个随机异常,因为你引用了not guaranteed,所以首先调用return fasle(我理解这是随机的),但幸运的是,console.log 总是首先被调用。 谢谢 T.J.克劳德,e.stopImmediatePropagation(); 工作。但它必须任何其他事件监听器之前定义。并且处理程序的执行顺序确实是从上到下,至少在我的测试中没有你引用not guaranteed的那么多。感谢您提供详细信息。

以上是关于多个 addEventListener 如何在 JavaScript 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

为同一表单提交多个 addEventListener

您可以使用 addEventListener (javascript) 区分多个变量吗?

绑定事件 addEventListener

addEventListener与onclick的区别

js如何取消鼠标滚轮绑定的事件

attachEvent和addEventListener