事件委托,也称为事件代理,一般来说,它是把一个或一组元素的事件委托到其父层或者更外层的元素上,《JavaScript高级程序设计》书上描述事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,下面来看一个取快递的例子:
假设班上有五十个同学需要去取快递,如果每一个人都需要自己去取的话,那么不仅浪费时间,而且还会占据快递点大量的空间,而如果这五十个人的快递都指定班长一个人去领,班长再根据快递上的名字进行分发,那么其效率就能大大提高。
上述的例子中班长就代理了五十名同学取快递的事件,使得五十个人的事件就委托给了一个人去做,DOM事件委托也就是根据这样的原理设计的,它有两大优点,一是可以节约内存,二是可以处理动态元素。
1.节约内存
如取快递的例子,将五十个人的事情都委托给班长一个人做,那么就能大大节约快递点的空间,不需要让五十名同学都来到快递点。如果现在有几十个 li 元素,给每一个 li 元素都设置点击事件,那么就需要设置几十个监听器,而如果根据事件冒泡机制,将事件监听设置在其父元素中,监听的过程中根据 e.target 得到当前触发事件的元素,再对该元素进行操作,这样就能大大减少内存的占用,因为只需要设置一个监听器,如下所示:
<body>
<ul>
<li>我是li 1</li>
<li>我是li 2</li>
<li>我是li 3</li>
<li>我是li 4</li>
<li>我是li 5</li>
......
</ul>
<script>
let ul = document.querySelector('ul');
ul.addEventListener('click', e => {
console.log(e.target.innerText);
})
</script>
</body>
2.处理动态元素
如果某一个元素并不是固定存在的,而是动态变化的,那么你就需要在元素存在的时候绑定事件,不存在的时候解除绑定的事件,如果不解除会浪费内存,这样就会很麻烦,而如果使用事件委托,将事件绑定在动态元素的父元素上,那么就可以不用去处理元素存不存在的问题了,如下例所示,能够访问暂时还不存在的span元素:
<body>
<div id="parent"></div>
<script>
let parent = document.getElementById('parent')
setTimeout(() => {
let span = document.createElement('span');
span.innerText = '我是子元素';
parent.appendChild(span);
}, 1000);
parent.addEventListener('click', e => {
console.log(e.target)
})
</script>
</body>
3.事件委托的局限性
事件委托也有一定的局限性,比如 focus、blur 之类的事件本身没有事件冒泡机制,所以无法采用事件委托,对于mousemove、mouseout 这类虽然有事件冒泡的事件,但是需要不断通过位置去计算定位,这样就比较消耗性能,因此也不适合使用事件委托。
4.事件委托函数
最后附上一个比较完整的事件委托的函数,在封装该函数的时候,主要要考虑到这种情况:假设你要给多个 li 元素监听点击事件,那么就将监听放在其父元素 ul 中,但是如果 li 元素中还嵌套有 span 元素,这样当你点击的时候就会很容易点击到 span 元素上,而不是 li 元素,所以 e.target 就应该逐层向上传递一下,看是否有匹配的 li 元素,当 e.target 等于本身监听的 ul 元素的时候就表示没有符合的元素,于是便结束循环:
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (el === element) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}
欢迎大家留言讨论。