假设您想将点击处理程序附加到 <button>
元素。您几乎肯定需要这样做,因为在 <form>
元素之外,按钮如果没有 JavaScript 的帮助是不会有任何作用的。您可以使用如下代码来实现:
var button = document.querySelector("button");
button.addEventListener("click", function(e) {
// button was clicked
});
但这并没有使用事件委托。
事件委托是指您不将点击处理程序直接绑定到元素本身,而是绑定到 DOM 树中更高层的元素。这样做的目的是,您可以随时在其中移除和插入新的 DOM 元素,而无需担心事件会被销毁并需要重新绑定。
假设我们的按钮包含一个齿轮图标
<button>
<svg>
<use xlink:href="#gear"></use>
</svg>
</button>
并且我们通过监视 document 元素上的点击来绑定它
document.documentElement.addEventListener("click", function(e) {
});
我们如何知道点击是否发生在按钮上?我们可以使用事件的目标来判断
document.documentElement.addEventListener("click", function(e) {
console.log(e.target);
});
这里就变得有点棘手了。在这个例子中,即使用户点击按钮上的某个位置,根据他们点击的具体位置,e.target
可能是:
- 按钮元素
- svg 元素
- use 元素
所以,如果您希望能够像这样操作:
document.documentElement.addEventListener("click", function(e) {
if (e.target.tagName === "BUTTON") {
// may not work, because might be svg or use
}
});

不幸的是,它不会那么简单。无论您检查类名、ID 还是其他任何内容,您期望的元素本身都可能是不正确的。
有一个非常不错的 CSS 修复方案... 如果我们确保按钮内部没有任何元素具有 pointer-events 属性,则按钮内部的点击将始终针对按钮本身
button > * {
pointer-events: none;
}
这也能防止其他 JavaScript 代码阻止事件冒泡到按钮本身(或更高层)的情况。
document.querySelector("button > svg").addEventListener("click", function(e) {
e.stopPropagation();
e.preventDefault();
});
document.querySelector("button").addEventListener("click", function() {
// If the user clicked right on the SVG,
// this will never fire
});

难道直接通过 id、class 或其他一些数据属性来绑定事件不是更好吗?这样 DOM 元素本身就不重要了,而且可以更好地实现关注点分离。使用 CSS 来处理事件似乎有点乱。
就安全地处理委托监听器而言,使用
closest
来委托事件处理程序相对容易(并且通过使用matches
来模拟行为也相对简单)我在 codepen 上有一个 简单的事件委托示例
只需使用
event.currentTarget
,它保存了最初附加事件监听器的元素。一旦你习惯了它,生活就会变得轻松很多;)event.currentTarget
太棒了。根据场景的不同,我通常会同时使用 currentTarget 和 pointer-events: none。不过,一旦你制定了一个规则,就会发现下一个例外……
是的,我会这么做
我总是检查处理程序中的
element.closest('.click-target-selector')
,但这要好得多!谢谢!
@ashley sheridan
有时,如果您需要对表格行执行 1000 次点击事件,最好使用一个事件进行委托(当表格内容发生变化时,您无需重新附加它)。
您仍然可以允许事件冒泡到所有元素,然后在您要使用该事件的元素处停止冒泡吗?
另一种方法
我使用 CSS 解决了这个问题。我从不使用不可交互的元素来侦听 DOM 事件,因此我只需要担心几个元素
效果很好。
顺带一提,以下代码可以防止在疯狂点击交互式元素时选择文本
这个 closest() 用户印象深刻。谢谢!
或者如果是一个简单的案例,只需使用老式的属性“onclick”。
我不理解这句话:“这样做的目的是,您可以随时在其中移除和插入新的 DOM 元素,而无需担心事件会被销毁并需要重新绑定。”。
我一直都在我的按钮上直接添加事件监听器。这有问题吗?
我相信他指的是为动态元素添加事件监听器。如果您要附加到 DOM 上尚不存在的按钮的监听器,则事件委托是一种处理它的方法。
您可以从目标节点向上遍历,包括目标节点本身。如果您遇到特定模式(我更喜欢 data-attributes),您就可以获得观察到的目标。
示例
现在,您从 event.target 向上遍历每个点击事件,直到找到属性“data-clickhandler”或到达您为其分配事件处理程序的根元素。许多点击将向上遍历到根元素而没有匹配项,但这并不会真正降低现代引擎的速度。
一旦找到目标,您就可以从相同的数据属性中获取所需的的行为。由您决定找到合适的设 计模式。我更喜欢简单的查找表。
这甚至可以处理具有相同目标元素的不同事件
在原生 JS 中实现遍历并不太难。JQuery 有相应的方法。遗憾的是,prototype.js 现在不太合适了;它们有一个专门的事件方法就是为了这个策略。
或者直接使用 jQuery
jQuery 在这里并不能自动帮到你。内部元素上的任何停止传播都会像在其他任何地方一样让你在 jQuery 中陷入困境。
Chris,太棒了,我以前不知道这个,我肯定会用它!
谢谢。