事件委托

Tags
Published
Author

浪费资源和影响性能

在通常情况下,我们给一组DOM节点添加相同的事件,都是使用循环绑定
Array.from(document.querySelectorAll('li')).map(li => li.addEventListener('click', handler));
当节点数量比较少的时候,这是最简单直接的方法,没有问题。但是当节点数量达到一定数量级的时候(数千甚至上万,比如表格应用),给这么多个节点都循环绑定相同一个事件,显然会造成资源浪费,而且影响性能。
为什么说浪费资源和性能呢?
首先,在初始化页面的时候,我们先要对这些节点进行一次遍历,
DOM节点(也就是HTMLElement)是一个很大很庞杂的对象
,遍历DOM节点要比遍历普通数组更耗时。如下图:
notion image
这只是一个简单的只有文本子节点的li,这也说明了为什么使用虚拟DOM会更快。
第二,在我们遍历节点的同时,我们还要给每个节i点添加事件,也就是给节点(或者其_proto_)添加二级DOM事件。
另外,大家都知道,为每个DOM节点绑定事件后都会有一个event对象被当做参数传入事件函数里面,event对象包含了当前事件发生的信息,这个event对象也是一个很庞大的对象
notion image
Event对象
所以,总结起来,大量DOM节点的事件绑定造成资源浪费和性能损失原因主要有三点: 1. DOM节点循环的耗时 2. 给每个DOM节点添加事件 3. 每个event对象的创建

事件委托

在了解事件委托之前,我们首先要知道浏览器规定的概念:
DOM2 级事件规定的事件流包括三个阶段:事件捕获、目标阶段、事件冒泡。
  • 事件流:描述的是从页面中接收事件的顺序
  • 事件冒泡:事件开始由最具体的元素接收,然后逐级向上传播到较为不具体的节点或文档。
  • 事件捕获:事件开始由不太具体的节点接收,然后逐级向下传播到最具体的节点。它与事件冒泡是相反的过程
notion image
事件流
click 事件发生的顺序 :
<ul id="myLink"> <li id="1">aaa</li> <li id="2">bbb</li> <li id="3">ccc</li></ul>
事件冒泡 li -> ul -> body -> html -> document ->window
事件捕获 window -> document-> html -> body -> ul ->li

事件委托概念

通俗的说就是将元素的事件委托给它的父级或者更外级的元素处理,它的实现机制就是事件冒泡

不使用事件委托

var myLink = document.getElementById("myLink");var li = myLink.getElementsByTagName("li");for (var i = 0; i < li.length; i++) { li[i].onclick = function (e) { var e = event || window.event; var target = e.target || e.srcElement; alert(e.target.id + ":" + e.target.innerText); };}
  • 给每一个列表都绑定事件,消耗内存
  • 当有动态添加的元素时,需要重新给元素绑定事件
function delegate(element, eventType, selector, fn) { element.addEventListener(eventType, (e) => { let el = e.target; while (!el.matches(selector)) { if (element === el) { el = null; break; } el = el.parentNode; } el && fn.call(el, e, el); }); return element;}

事件委托的优点

  • 只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有的元素都绑定事件减少内存占用空间,提升性能。
  • 动态新增的元素无需重新绑定事件
事件委托的实现依靠的冒泡,因此不支持事件冒泡的事件就不适合使用事件委托。
不是所有的事件绑定都适合使用事件委托,不恰当使用反而可能导致不需要绑定事件的元素也被绑定上了事件。

addEventListener 监听器方法第三个参数

element.addEventListener(event, function, useCapture)
useCapture 可选。布尔值,指定事件是否在捕获或冒泡阶段执行。
可能值:
  • true - 事件句柄在捕获阶段执行(即在事件捕获阶段调用处理函数)
  • false- 默认。事件句柄在冒泡阶段执行(即表示在事件冒泡的阶段调用事件处理函数)

阻止事件冒泡

  1. 给子级加 event.stopPropagation( )
$("#div1").mousedown(function (e) { var e = event || window.event; event.stopPropagation();});
  1. 在事件处理函数中返回 false
$("#div1").mousedown(function (event) { var e = e || window.event; return false;});
但是这两种方式是有区别的。return false 不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()则只阻止事件往上冒泡,不阻止事件本身。
  1. event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;
event.currentTarget 告诉我们该事件附加在哪个元素上,或者其 eventListener 触发了该事件的元素。 event.target 告诉事件从哪里开始。
给父节点绑定事件做冒泡的事件代理的原因是:evt.target 永远拿到的都是最深层的目标节点,在点击节点的时候,由于冒泡的原因,触发了父节点的点击事件,这个时候可以通过 evt.target 拿到真正点击的深层子节点,然后做进一步逻辑

注意点

  1. 事件流顺序
  1. 事件委托原理->事件冒泡的应用
  1. addEventListener 第三个参数 默认是 false 事件冒泡时触发 true 为事件捕获阶段触发
  1. e.target 和 e.currentTarget 的区别