为什么需要事件委托
什么是事件委托?事件委托的原理又是什么?事件冒泡和事件捕获的区别又在哪里?
要解答上面三个问题,我们要一步步弄清楚事件机制:事件流 -> 事件冒泡 -> 事件捕获 -> 事件委托
事件流描述了页面接收事件的顺序。IE 和 Netscape 开发团队提出了几乎完全相
反的事件流方案。IE 支持事件冒泡流,而 Netscape 支持事件捕获流。
事件冒泡
IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触
发,然后向上传播至没有那么具体的元素(文档)。比如有如下 HTML 页面:
1 |
|
在点击页面中的
也就是说,
路向上,在经过的每个节点上依次触发,直至到达 document 对象。
事件捕获
Netscape 团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节
点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标
前拦截事件。如果前面的例子使用事件捕获,则点击
在事件捕获中,click 事件首先由 document 元素捕获,然后沿 DOM 树依次向下传播,直至到达
实际的目标元素
由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡,特殊情
况下可以使用事件捕获。
事件委托
“过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件
处理程序来管理一种类型的事件。例如,click 事件冒泡到 document。这意味着可以为整个页面指定
一个 onclick 事件处理程序,而不用为每个可点击元素分别指定事件处理程序。
比如有以下HTML:
1 | <ul id="myLinks"> |
这里的 HTML 包含 3 个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定 3
个事件处理程序:
1 | let item1 = document.getElementById("goSomewhere"); |
如果对页面中所有需要使用 onclick 事件处理程序的元素都如法炮制,结果就会出现大片雷同的
只为指定事件处理程序的代码。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,
就可以解决问题。比如:
1 | let list = document.getElementById("myLinks"); |
这里只给
- 元素添加了一个 onclick 事件处理程序。因为所有列表项都是这个
- document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded
- 节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也
- 减少整个页面所需的内存,提升整体性能。
元素的后代,所以它们的事件会向上冒泡,最终都会由这个函数来处理。但事件目标是每个被点击的列
表项,只要检查 event 对象的 id 属性就可以确定,然后再执行相应的操作即可。相对于前面不使用事
件委托的代码,这里的代码不会导致先期延迟,因为只访问了一个 DOM 元素和添加了一个事件处理程
序。结果对用户来说没有区别,但这种方式占用内存更少。所有使用按钮的事件(大多数鼠标事件和键
盘事件)都适用于这个解决方案。
只要可行,就应该考虑只给 document 添加一个事件处理程序,通过它处理页面中所有某种类型的
事件。相对于之前的技术,事件委托具有如下优点。
或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
可以节省时间。