JavaScript中的多个addEventListener如何工作?

18

一个文档中有两个脚本:

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

为了确保我的click事件不被其他脚本覆盖,我改用了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个回答

61

如果我想让我的脚本完全控制点击事件(就像无视其他脚本中定义的事件,总是返回false),该怎么办?

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

这里涉及到(至少)四个方面:

  1. 防止默认行为。

  2. 阻止传播到祖先元素。

  3. 阻止调用同一元素上的其他处理程序。

  4. 处理程序被调用的顺序。

按照顺序:

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不能阻止在同一元素上调用的其他处理程序。根据规范
stopPropagation方法用于在事件流期间防止事件进一步传播。如果此方法由任何EventListener调用,则事件将停止通过树进行传播。事件流停止之前,事件将完成对当前上的所有侦听器的分派。”
(强调是我的。)
3. 阻止在同一元素上调用其他处理程序
自然地,这在DOM0中没有出现,因为不能在同一元素上有相同事件的其他处理程序。 :-)
据我所知,在DOM2中没有办法做到这一点,但是DOM3为我们提供了stopImmediatePropagation
document.addEventListener("click", function(e) {
    e.stopImmediatePropagation();
}, false);

一些库提供此功能(即使在非 DOM3 系统(如 IE8)上连接到库的处理程序),请参见下面。

4. 调用处理程序的顺序

再次强调,这与 DOM0 无关,因为不可能有其他处理程序。

在 DOM2 中,规范明确表示不能保证以附加到元素的处理程序的顺序进行调用; 但是 DOM3 更改了该规定,指出处理程序按照注册它们的顺序进行调用。

首先来自 DOM2第1.2.1节

虽然将接收到的任何事件都保证触发事件目标上的所有 EventListeners,但是没有规定它们将根据其他 EventListeners 接收事件的顺序接收该事件。

但是,DOM3第3.1节取代了它:

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

(我强调。)

一些库保证顺序,只要您使用库连接事件。

值得注意的是,在 Microsoft 的 DOM2 前身(例如,attachEvent),与 DOM3 的顺序相反:处理程序按其注册的倒序调用。


因此,将#3和#4结合起来,如果您可以首先注册处理程序,则将首先调用它,并且您可以使用 stopImmediatePropagation 阻止调用其他处理程序。 前提是浏览器正确实现了 DOM3。


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


1
感谢T.J. Crowder,e.stopImmediatePropagation();有效。但它必须在任何其他事件侦听器之前定义。处理程序的执行顺序确实是从上到下,而不是像您引用的“不保证”的那样,在我的测试中至少如此。感谢您提供详细信息。 - user1643156
@MikkoRantalainen - 除非你能指出实际上哪里做错了,坦白地说,我认为这个警告在2020年根本没有用。DOM3事件是2003年的事情(现在已被https://www.w3.org/TR/uievents/取代)。即使是IE9,如果你使用`addEventListener`(而不是`attachEvent`),它也会正确处理顺序。(我对IE11很确定,但必须在IE9上测试才能确定。)除了IE之外,其他所有东西在2013年发布这篇答案之前就已经正确地处理了这个问题。这不会改变。 - T.J. Crowder
我的观点是,如果您想引用任何规范,所有指针都应该指向WHATWG。自从2003年DOM2以来,W3C未能使任何DOM规范定义事件超出“工作草案”状态。根据W3C的规定,“工作草案”规范不应在生产中使用。另一种选择是声明答案中列出的工作草案规则似乎适用于2020年的真实用户代理。有关详细信息,请参见https://www.w3.org/2004/02/Process-20040205/tr.html#maturity-levels。 - Mikko Rantalainen
@MikkoRantalainen - W3C有他们的说法,但实际情况并非如此。不过我现在大多数情况下都转向WHAT-WG规范了,尽管上面的链接除外。你手头有相关的链接吗? - T.J. Crowder
我想链接到 https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation,因为 W3C 仍然只将所有规范作为工作草案提及。事件派发的文档位于 https://dom.spec.whatwg.org/#concept-event-dispatch,如果您只关心现代 HTML5 兼容浏览器,则需要阅读该部分。 - Mikko Rantalainen
显示剩余11条评论

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