事件委托

事件委托,又称事件代理,是利用事件冒泡的特性,将本应该绑定在多个元素上的事件绑定在他们的祖先元素上,实现处理程序对多个子孙级元素的某类型事件管理。通俗来说,就是把任意个子孙级元素的响应事件的函数委托到另一个元素(通常为委托元素的祖先元素)。

优点

减少内存消耗

DOM 树层级较深,绑定事件越多,浏览器内存占用越大,严重影响性能。

在 JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断地与 DOM 节点进行交互,访问 DOM 的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间。如果使用事件委托,就会将所有的操作放到 JavaScript 程序中,与 DOM 操作就只需要交互一次,这样就能大大减少与 DOM 的交互次数,提高性能。

每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差。

动态绑定事件

很多场景需要开发者通过 AJAX 或者用户动态增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的。

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。

事件绑定解决方案

使用事件委托能有效解决下列问题。

  • 绑定事件越多,浏览器内存占用越大,严重影响性能
  • 局部刷新的盛行,导致每次加载完,都要重新绑定事件
  • 部分浏览器移除元素时,绑定的事件并没有被及时移除,导致的内存泄漏,严重影响性能
  • 大部分局部刷新,只是显示的数据,而操作却是大部分相同的,重复绑定,会导致代码的耦合性过大,严重影响后期的维护

缺点

  • 诸如 onfocusonblur 之类的事件本身没有事件冒泡机制,所以无法使用事件委托
  • mouseovermousemove 这样的鼠标、滚轮等事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合事件委托的

使用事件委托的注意事项:

  • 只在必须的地方,使用事件委托,例如:AJAX 的局部刷新区域
  • 尽量的减少绑定的层级,并且不在 <body> 元素上进行绑定(事件委托的原理离不开 DOM 的查找,而浏览器太多层级的查找非常耗性能)
  • 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。

优化手段

事件委托影响性能的因素:

  • 元素中绑定事件委托的次数
  • 点击的最底层元素,到绑定事件元素之间的 DOM 层数

结合这两点,可以在必须使用事件委托的场景下,以如下原则优化。

  • 只在必须的地方使用事件委托,比如网络请求的局部刷新区域
  • 尽量低减少绑定的层级,不在 <body> 元素上,进行绑定
  • 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发

最佳实践

查找列表中子项的索引

<body>
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<!-- 还有很多 -->
</ul>
<script type="text/javascript">
const list = document.querySelector('.list');
list.addEventListener(
'click',
function(e) {
let e = e || window.event;
let target = e.target || e.srcElement;
// 关键判断条件
const index = [].indexOf.call(target.parentElement.children, target);
console.log(index);
},
false
);
</script>
</body>

参考文章: