什么是事件代理

事件代理,可能是代理模式最常见的一种应用方式,也是一道实打实的高频面试题。它的场景是一个父元素下有多个子元素,像这样:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>事件代理</title>
  8. </head>
  9. <body>
  10. <div id="father">
  11. <a href="#">链接1号</a>
  12. <a href="#">链接2号</a>
  13. <a href="#">链接3号</a>
  14. <a href="#">链接4号</a>
  15. <a href="#">链接5号</a>
  16. <a href="#">链接6号</a>
  17. </div>
  18. </body>
  19. </html>

我们现在的需求是,希望鼠标点击每个 a 标签,都可以弹出“我是xxx”这样的提示。比如点击第一个 a 标签,弹出“我是链接1号”这样的提示。这意味着我们至少要安装 6 个监听函数给 6 个不同的的元素(一般我们会用循环,代码如下所示),如果我们的 a 标签进一步增多,那么性能的开销会更大。

  1. // 假如不用代理模式,我们将循环安装监听函数
  2. const aNodes = document.getElementById('father').getElementsByTagName('a')
  3. const aLength = aNodes.length
  4. for(let i=0;i<aLength;i++) {
  5. aNodes[i].addEventListener('click', function(e) {
  6. e.preventDefault()
  7. alert(`我是${aNodes[i].innerText}`)
  8. })
  9. }

考虑到事件本身具有“冒泡”的特性,当我们点击 a 元素时,点击事件会“冒泡”到父元素 div 上,从而被监听到。如此一来,点击事件的监听函数只需要在 div 元素上被绑定一次即可,而不需要在子元素上被绑定 N 次——这种做法就是事件代理,它可以很大程度上提高我们代码的性能。

事件代理的实现

用代理模式实现多个子元素的事件监听,代码会简单很多:

  1. // 获取父元素
  2. const father = document.getElementById('father')
  3. // 给父元素安装一次监听函数
  4. father.addEventListener('click', function(e) {
  5. // 识别是否是目标子元素
  6. if(e.target.tagName === 'A') {
  7. // 以下是监听函数的函数体
  8. e.preventDefault()
  9. alert(`我是${e.target.innerText}`)
  10. }
  11. } )

在这种做法下,我们的点击操作并不会直接触及目标子元素,而是由父元素对事件进行处理和分发、间接地将其作用于子元素,因此这种操作从模式上划分属于代理模式。

摘抄自,掘金小册《JavaScript 设计模式核⼼原理与应⽤实践》