js中的事件代理(event delegation)

JavaScript世界中的一个很热门的方法论就是事件代理,它可以使我们避免向特定的元素节点绑定事件监听,而是绑定在了一个父级节点。

这个事件监听回对冒泡上来的事件进行分析,找到所匹配的子元素。
这个概念其实很简单,但是还有很多人不太清楚事件代理是如何工作的。
本文将简要解释事件代理是如何工作的,同时提供一个基本的事件代理的实现。

举个栗子

比如说我们有一个父级ul元素,同时有一些子级元素节点:

1
2
3
4
5
6
7
8
<ul id="parent-list">
true<li id="post-1">Item 1</li>
true<li id="post-2">Item 2</li>
true<li id="post-3">Item 3</li>
true<li id="post-4">Item 4</li>
true<li id="post-5">Item 5</li>
true<li id="post-6">Item 6</li>
</ul>

需求是当每个元素被点击时,我们需要为每一个独立的li元素添加一个事件监听器,来处理相应的事件。

问题是
如果这些li元素需要被频繁的从列表中添加和移除时,添加和移除事件监听器很麻烦,特别是当执行这些添加和移除的代码是app中其他地方的。

较好的解决方案是,把事件监听器添加到li的父级节点ul,但是这样一来,我们怎么知道是哪个元素被点击了呢?

答案
当事件冒泡到了ul元素时,我们手动检查事件对象的目标(target)属性,获取到真正被点击的节点。
下面是一个非常简单和基础的js代码片段来解释什么是事件代理:

1
2
3
4
5
6
7
8
document.getElementById('parent-list').addEventListener("click", function(e) {
true// e.target 是被点击的元素
true// 注意eventobject的浏览器兼容性
trueif (e.target && e.target.nodeName == 'LI') {
truetrue// 检测到目标节点是li元素,输出对应的id
truetrueconsole.log('list item', e.target.id.replace('post-', ''), ' was clicked!');
true}
});

上面的代码首先给父级节点添加了click事件监听器,当被触发时,检查事件的目标元素来确定是不是想要操作的对象:
如果是,则运行相对应的代码;如果不是,则该事件就会被忽略。

这个例子是有点儿简单,但足以说事件代理的原理。可能在判断事件目标元素时有些负责,需要各种选择器来辅助。

下面看jQuery中的用法,使用.on函数,结合其强大的元素选择器,就能方便的实现事件代理。

1
.on( events [, selector ] [, data ], handler )

最后总结一下,事件代理使用的场景和需要注意的地方。

  1. 待绑定的对象元素是动态的,需要频繁的添加或者删除等;
  2. 待绑定的元素很多,比如一个100行的Table,需要对tr绑定事件;
  3. 被绑定的父级对象离目标对象的层级越少越好,否则会有性能问题,尽量不要直接绑定到document或者body上
  4. 选择器越简单越好,会提升性能
  5. 事件类型要注意是能冒泡的,比如focusblur本身是不会冒泡的