也许你在学习前端知识或者面试题的时候,有了解到一个问题叫做 事件委托,或许你也可能背了一些死的定理,但你有真正了解过这个事件委托 该怎么做,什么时候做吗?
在学习 事件委托 之前需要先学习一下 事件捕获 和 事件冒泡
捕获 和 冒泡 是指 DOM树 处理事件的两种不同的方式,简单理解可以视为两个不同阶段
事件捕获
当某个地方的时候,会从 DOM树 的根节点出发直到到达你点击的地方,就好像警察知道了犯人在哪个地方,可以迅速出动去抓捕,这个过程叫做捕获
事件冒泡
当捕获阶段结束后,会从捕获的目标处向 DOM树 扩散,就好比锅里的沸腾的水从底部一直向高处咕噜咕噜冒泡,这个阶段就是冒泡阶段
看到这里,你应该已经明白了这两者的先后顺序和特点,是的,捕获阶段 先于 冒泡阶段
思考一下,下面的代码的先后输出是什么
<div id="parent">
<div id="child">
<button id="btn">Click me</button>
</div>
</div>
<script>
const parent = document.getElementById('parent');
const child = document.getElementById('child');
const btn = document.getElementById('btn');
// 在事件捕获阶段触发事件处理程序
parent.addEventListener('click', function() {
console.log('Parent');
}, true);
child.addEventListener('click', function() {
console.log('Child');
}, true);
btn.addEventListener('click', function() {
console.log('Button');
});
</script>
输出顺序:Parent Child Button
刚好符合 捕获阶段 的先后顺序,点击按钮的时候,先是从 根节点(parent)到子节点(child)再到子节点的子节点(button)
值得注意的是,我们给 事件监听器(addEventListener)的第三个参数(事件传播阶段)设置了 true,当为 true 的时候,启动的事件捕获阶段
相反,设为false也是默认状态的时候,就是冒泡阶段,这个时候的输出顺序就完全不一样了,当为冒泡阶段的时候
输出顺序:Button Child Parent
如果你想捕获和冒泡的时候都各自触发一次,也可以,但是需要再绑定一个事件
// 在事件捕获阶段触发事件处理程序
parent.addEventListener('click', function() {
console.log('Parent capture');
}, true);
// 在事件冒泡阶段触发事件处理程序
parent.addEventListener('click', function() {
console.log('Parent pop');
}, false);
事件委托
你一定会说,事件委托就是把绑定在 子级元素 的事件绑定在 父级元素 里面,比如把绑定在 li 标签的事件绑定在 ul 上,这样就可以提高代码性能和避免添加过多的事件处理
你说的很对,请你用代码演示一下具体怎么写呢,如果我是面试官我就会这样问你
可能此时的你瞬间就慌了:完了我只会说不会写呀
一个班级里面有很多学生,但是只有一个老师,老师要让同学上黑板写字,如果给每个同学都发一根粉笔就会造成大量的开销,特别是当学生特别多的时候,但是每次上黑板来写只有一个学生,所以可以让老师拥有粉笔,当点到对应的同学的时候就把粉笔给他让他写,这样就只需要一个粉笔就能同样完成需求或任务
在这里以Vue2为例
<template>
<div>
<ul @click="handleGetItem">
<li class="item" v-for="item in list" :key="item.id" :data-id="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ id: 1, name: 'Asuka' },
{ id: 2, name: 'Nikaido' },
{ id: 3, name: 'Nishino' },
{ id: 4, name: 'Shizuka' },
]
}
},
methods: {
// 获取具体点击的是谁
handleGetItem(e) {
const target = e.target;
// 确保你点击的是 类名为 item 的标签
if (target.closest('.item')) {
// dataset是一个对象,它包含的信息主要是你在标签上设置的标识,比如 :data-id
// data- 是固定的,id可以是其他的 wd,dd 之类的
const itemID = target.closest('.item').dataset.id;
this.handleSomething(itemID);
}
},
// 拿到它的信息去做你真正想处理的事情
handleSomething(itemID) {
console.log(itemID);
}
}
}
</script>
如果要用事件委托,就一定要在每一个 子级元素 绑定一个标识,这里不是指 key,而是 js 可以拿到的 :data-xx
它会把 :data-xx 的 xx 作为一个对象的 键值对 的键,值自然就是你绑定的值,item的id
当我们点击每一个 item 的时候,事件会冒泡,冒泡到父级元素,然后父级元素可以根据你得到你点击的 item 的标识去获取到你具体点的是哪个 item,这样也是拿到了你需要操作的元素了
如果你认真看完了这篇文章,相信你对事件捕获、事件冒泡、事件委托都有了更深入跟深刻的了解,当下次面试的时候,还可以稍微操作一手让面试官惊讶一下
最后,还是希望对你有帮助,下次再见!