我们知道,在 HTML 中为一个元素绑定事件,需要对这个元素添加事件监听,如 onclick
属性或者 addEventListener()
。1
2
3
4
5
6
7
8<ul id="list">
<li id="item-1" onclick="console.log('I am Item 1)">Item 1</li>
<li id="item-2" onclick="console.log('I am Item 2)">Item 2</li>
<li id="item-3" onclick="console.log('I am Item 3)">Item 3</li>
<li id="item-4" onclick="console.log('I am Item 4)">Item 4</li>
<li id="item-5" onclick="console.log('I am Item 5)">Item 5</li>
<li id="item-6" onclick="console.log('I am Item 6)">Item 6</li>
</ul>
1 | document.querySelector('#item-1').addEventListener('click', () => { console.log('I am Item 1') }) |
这样看起来实在是太笨啦!并且在动态增减列表元素的场景下,它无法及时地添加或移除事件监听。怎么让事情变得更简单些呢?那就是使用事件委托来进行监听:1
2
3
4
5document.querySelector('#list').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log(`I am ${e.target.textContent}`)
}
})
代码是不是立马就变得清爽起来~我们只用一个事件监听就实现在每个 <li>
元素点击时都能执行某些操作。
但现实中,我们的页面结构往往不会这么简单,需要产生交互的元素里面可能包含了很多子元素,例如图标、被其他标签包裹的文本等,这使得 event.target
不一定是目标元素。这个时候我们就得对当前点击的元素逐层向上寻找父元素,若找到目标元素,则说明我们需要执行对应的操作。所以,我们需要写一个更通用的事件委托:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function delegate(eventType, targetSelector, fn) {
document.addEventListener(eventType, (e) => {
let currentEl = e.target;
while(!currentEl.matches(targetSelector)) { // 通过选择器来匹配,而不是写死tagName匹配
if(currentEl === document.documentElement) { // 当已经查到顶后,及时停止向上查找
currentEl = null;
break;
}
currentEl = currentEl.parentElement;
}
if(currentEl) {
fn(currentEl);
}
})
}
delegate('click', 'li', () => { ... })
最后,你可以到这里来 Try And Code 一下。