事件委托
事件委托其实不是什么特别晦涩难懂的知识,其背后的知识点是关于事件的机制 (事件冒泡)。
我们知道,为单独一个元素注册事件监听器可以使用 on<event>
、 元素行内属性和 addEventListener()
这些方法轻松实现,但是,当这些元素的数量递增时:
<ul id="parent">
<li id="child1">child</li>
<li id="child2">child</li>
<li id="child3">child</li>
<li id="child4">child</li>
<li id="child5">child</li>
...
</ul>
再像以前那样为每一个子元素绑定一个监听器明显是个坏主意。
在说解决方案 (就是事件委托😂) 前,首先要说一下关于事件触发循序的两个模型 (model):事件捕获 (Event capturing) 和事件冒泡 (Event bubbling)。
Netscape vs Microsoft !!
Netscape said that the event on element1 takes place first. This is called event capturing.
Microsoft maintained that the event on element2 takes precedence. This is called event bubbling.
因此, W3C 机智的采取中立模型 (W3C model),即保留两个模型。将事件流分为三个阶段,如下图:
在捕获阶段:
浏览器检查元素的最外层祖先
<html>
(若document
或 全局对象也注册了处理程序,则它们先) ,是否在捕获阶段中注册了一个onclick
事件处理程序,如果是,则运行它。然后,它移动到
<html>
中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。
在目标阶段:
- 事件对象到达事件对象的目标(元素),若冒泡阶段不冒泡(即捕获完毕),事件流将会在这个阶段停止。
在冒泡阶段,恰恰相反:
浏览器检查实际点击的元素是否在冒泡阶段中注册了一个
onclick
事件处理程序,如果是,则运行它然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达
<html>
元素(若document
或 全局对象也注册了处理程序,则它们最后)。
由图可以知道,在 W3C模型 中,事件捕获阶段发生时间是比事件冒泡要早的。而且 W3C 推崇使用的是 addEventListener(type, listener, useCapture)
这个方法来实现两者兼存的,其中第三个参数接收一个布尔值,当该值为 true
时,该元素事件触发执行事件捕获阶段。
看以下代码:
<body onclick="console.log('body!')"> <!-- 这种方式无法使用事件捕获 -->
<div id="parent">
<div id = "child">
</div>
</div>
</body>
添加监听器:
const parent = document.getElementById("parent");
const child = document.getElementById("child");
parent.addEventListener("click", () => {
console.log("parent!");
},true); // 开启捕获阶段
child.addEventListener("click", () => {
console.log("child!");
},false); // 关闭捕获阶段(默认)
//当点击child元素时,输出以下
//parent!
//child!
//body!
可以使用 event.stopPropagation()
这个方法阻止事件的捕获或者冒泡。
上面的 parent 元素改为:
parent.addEventListener("click", e => {
console.log("parent!");
console.log("stopPropagation!");
e.stopPropagation();
});
//此时输出为:
//parent!
//stopPropagation!
所以我们直接回到事件委托,有了上面的知识后,事件委托其实就很容易理解了;事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。
回接上面代码,我们直接给父 ul 注册监听器就可以解决问题:
const parent = document.getElmentById("parent");
parent.addEventListener("click", e => {
let target = e.target; // 目标阶段,获取事件对象的事件目标
switch(target.id){
case "child1":
console.log("我是老大!");
break;
case "child2":
console.log("我是老二!");
break;
case "child3":
console.log("我是老三!");
break;
// ...
}
}, false);
总之,这本身是没有什么特别的东西,主要是对事件捕获和冒泡的一种复习。后续再扯一下自定义事件和冒泡的实现。😂
Reference:
JavaScript高级程序设计