事件委托原理

事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。

委托的优点

  1. 减少内存消耗
    试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;
    1. <ul id="list">
    2. <li>item 1</li>
    3. <li>item 2</li>
    4. <li>item 3</li>
    5. ......
    6. <li>item n</li>
    7. </ul>
    8. // ...... 代表中间还有未知数个 li
    如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;
    因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;
    所以事件委托可以减少大量的内存消耗,节约效率。
    2. 动态绑定事件
    比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件;
    在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;
    如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;
    所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的
    假定我们有一个UL元素,它有几个子元素:
    1. <ul id="parent-list">
    2. <li id="post-1">Item 1</li>
    3. <li id="post-2">Item 2</li>
    4. <li id="post-3">Item 3</li>
    5. <li id="post-4">Item 4</li>
    6. <li id="post-5">Item 5</li>
    7. <li id="post-6">Item 6</li>
    8. </ul>

我们还假设,当每个子元素被点击时,将会有各自不同的事件发生。你可以给每个独立的li元素添加事件监听器,但有时这些li元素可能会被删除,可能会有新增,监听它们的新增或删除事件将会是一场噩梦,尤其是当你的监听事件的代码放在应用的另一个地方时。但是,如果你将监听器安放到它们的父元素上呢?你如何能知道是那个子元素被点击了?
简单:当子元素的事件冒泡到父ul元素时,你可以检查事件对象的target属性,捕获真正被点击的节点元素的引用。下面是一段很简单的JavaScript代码,演示了事件委托的过程:

  1. // 找到父元素,添加监听器...
  2. document.getElementById("parent-list").addEventListener("click",function(e) {
  3. // e.target是被点击的元素!
  4. // 如果被点击的是li元素
  5. if(e.target && e.target.nodeName == "LI") {
  6. // 找到目标,输出ID!
  7. console.log("List item ",e.target.id.replace("post-")," was clicked!");
  8. }
  9. });

第一步是给父元素添加事件监听器。当有事件触发监听器时,检查事件的来源,排除非li子元素事件。如果是一个li元素,我们就找到了目标!如果不是一个li元素,事件将被忽略。这个例子非常简单,ULli是标准的父子搭配。让我们试验一些差异比较大的元素搭配。假设我们有一个父元素div,里面有很多子元素,但我们关心的是里面的一个带有”classA” CSS类的A标记:

  1. // 获得父元素DIV, 添加监听器...
  2. document.getElementById("myDiv").addEventListener("click",function(e) {
  3. // e.target是被点击的元素
  4. if(e.target && e.target.nodeName == "A") {
  5. // 获得CSS类名
  6. var classes = e.target.className.split(" ");
  7. // 搜索匹配!
  8. if(classes) {
  9. // For every CSS class the element has...
  10. for(var x = 0; x < classes.length; x++) {
  11. // If it has the CSS class we want...
  12. if(classes[x] == "classA") {
  13. // Bingo!
  14. console.log("Anchor element clicked!");
  15. // Now do something here....
  16. }
  17. }
  18. }
  19. }
  20. });

上面这个例子中不仅比较了标签名,而且比较了CSS类名。虽然稍微复杂了一点,但还是很具代表性的。比如,如果某个A标记里有一个span标记,则这个span将会成为target元素。这个时候,我们需要上溯DOM树结构,找到里面是否有一个 A.classA 的元素。
因为大部分程序员都会使用jQuery等工具库来处理DOM元素和事件,我建议大家都使用里面的事件委托方法,因为这里工具库里都提供了高级的委托方法和元素甄别方法。
希望这篇文章能帮助你理解JavaScript事件委托的幕后原理,希望你也感受到了事件委托的强大用处!