在 JavaScript 中事件可以被捕获和进行冒泡,捕获和冒泡允许我们实现一种被称为 事件委托 的强大的事件处理模式。
这个想法是,如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上。
假如我们有一个表格,该表格有9999+个单元格,我们想给每个被点击的单元格设置上选中颜色,与其为每个 <td>
(可能有很多)分配一个 onclick
处理程序 —— 我们可以在 <table>
元素上设置一个“捕获所有”的处理程序。
代码如下:
var selectedTd; table.onclick = function(event) { let target = event.target; // 在哪里点击的? if (target.tagName != 'TD') return; // 不在 TD 上?那么我们就不会在意 highlight(target); // 高亮显示它 }; function highlight(td) { if (selectedTd) { // 移除现有的高亮显示,如果有的话 selectedTd.classList.remove('highlight'); } selectedTd = td; selectedTd.classList.add('highlight'); // 高亮显示新的 td }
这样可以解决数量上的问题,但是还有个问题就是如果<td>中还嵌套了其他的标签呢,类似<strong></strong>
自然地,如果在该 <strong>
上点击,那么它将成为 event.target
的值。
为了规避掉这种情况,我们应该确定是否实在<td>内,所以改进代码如下所示:
table.onclick = function(event) { var td = event.target.closest('td'); // (1) if (!td) return; // (2) if (!table.contains(td)) return; // (3) highlight(td); // (4) };
elem.closest(selector)
方法返回与selector
匹配的最近的祖先。在我们的例子中,我们从源元素开始向上寻找<td>
。
<div id="box"> Box <strong>strong</strong> </div> <script> box.onclick = function (event) { console.log('event.target:',event.target.closest("div")); } </script>
用事件委托来实现一个可以展开关闭的树形菜单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JavaScript 代理模式</title> <script src="index.js" defer ></script> <style> li { cursor: pointer; } </style> </head> <body> <ul id="classify"> <li> 动物 <ul> <li> 鱼类 <ul> <li>草鱼</li> <li>鲤鱼</li> <li>黑鱼</li> </ul> </li> <li> 鸟类 <ul> <li>喜鹊</li> <li>乌鸦</li> <li>孔雀</li> </ul> </li> </ul> </li> <li> 植物 <ul> <li> 花类 <ul> <li>月季</li> <li>樱花</li> <li>牡丹</li> </ul> </li> <li> 草类 <ul> <li>水草</li> <li>野草</li> <li>小草</li> </ul> </li> </ul> </li> </ul> </body> </html>
// index.js let classify = document.getElementById('classify'); classify.onclick = function ({target}) { if (target.tagName !== 'LI') { return; } let firstElementChild = target.querySelector("ul"); if (!firstElementChild) { return; } firstElementChild.hidden = !firstElementChild.hidden; }
总结:
好处:
- 简化初始化并节省内存:无需添加许多处理程序。
- 更少的代码:添加或移除元素时,无需添加/移除处理程序
- DOM 修改 :我们可以使用
innerHTML
等,来批量添加/移除元素
坏处:
- 首先事件必须得冒泡,但是有些事件并不能冒泡
- 容器级别的处理程序会对容器中任意位置的事件做出反应,不管我们是否需要,可能会增加性能消耗