为什么会有事件委托?
- 一个列表中有多个li,都需要绑定监听函数,用for循环能做到,但性能差。
- 目前不存在的元素怎么添加监听函数?用JS一直监听元素是否出现再绑定吗?过程复杂,性能也差。
什么是事件委托?
事件委托、事件代理都是同一个东西。委托就是让他人帮你做你本应该做的事。事件委托就是子元素的事件监听委托给父元素、祖先元素监听。
原理
上一篇文章讲了事件模型,事件委托就是利用了事件模型的运行机制(window->最里层元素->window),将事件的监听委托给祖先元素。
实现一
<body>
<ul id="demo">
<li>li1</li>
<li>li2</li>
<li>li3</li>
<li>li4</li>
<li>li5</li>
<li>li6</li>
<li>li7</li>
<li>li8</li>
<li>li9</li>
<li>li10</li>
</ul>
<script>
demo.addEventListener("click", () => {
console.log("没有给li绑定事件,但点击了li,都会执行demo的监听函数");
});
</script>
</body>
但该实现有个问题,假如ul里面有其他元素呢?(其实ul里面最好只放li元素,可以想成div里面有多种元素,只想其中一种元素点击了才执行监听函数。)那么就需要加些代码判断了。
实现二
target:触发事件的元素
currentTarget:绑定了监听函数的元素
tagName:可获取元素的标签名
toLowerCase():将字符串转成小写形式
修改后的代码:
demo.addEventListener("click", (e) => {
if (e.target.tagName.toLowerCase() === "li") {
console.log("没有给li绑定事件,但点击了li,都会执行demo的监听函数");
}
});
然而这段代码还有有点问题。 HTML代码部分改成
<ul id="demo">
<li><span>li1</span></li>
<li><span>li2</span></li>
<li><span>li3</span></li>
<li><span>li4</span></li>
<li><span>li5</span></li>
<li><span>li6</span></li>
<li><span>li7</span></li>
<li><span>li8</span></li>
<li><span>li9</span></li>
<li><span>li10</span></li>
</ul>
点击li中的文字,发现控制台没有打印任何东西。是事件没有传播上去吗?不是,而是target判断出现了问题,此时target是span元素,自然不会进入if语句当中。当span元素又的确是li的一部分,该怎么让他执行呢?
实现三
demo.addEventListener("click", (e) => {
let el = e.target;
while (!(el.tagName.toLowerCase() === "li")) {
if (el === demo) { // 必须先判断是否等于祖先元素,否则死循环。
el = null;
break;
} else {
el = el.parentNode;
}
}
el && console.log("没有给li绑定事件,但点击了li,都会执行demo的监听函数");
});
当我们发现target不是li时,通过递归查找target的祖先元素是不是li。
但要注意递归的范围。target <= li < demo
跳出递归后也要判断一下el是否为空,即有没有找到祖先元素,找到了才执行log语句。
总结
我们可以发现事件委托说简单也简单,说难也难。原理利用了事件模型的运行机制,但是利用的过程中要考虑多方面的因素。实现三是较好的事件委托,考虑了target的子元素点击和target兄弟元素点击下的触发情况。读者可以将实现三封装成一个函数,当你封装好了,也就可以说掌握了事件委托。最后,谢谢您的阅读!