在聊React事件机制之前,我们需要先理解javascript原生事件机制
javascript原生事件机制主有两个核心概念:
- 事件流2. 事件委托(代理)注:这次重点讲的是机制,涉及事件兼容的问题不在本次讨论范围内
事件流
javascript的事件流: 通俗的讲是由外到里捕获再从里到外冒泡的过程
写一个元素嵌套的例子, document → outer → inner
ini
部分代码:
<div class = 'outer'>
outer
<div class = 'inner'>
inner
</div>
</div>

当我们点击inner的时候它的事件流如下:

从上图我们可以看到当点击inner的时候:
-
外到里执行事件捕获,inner获取到元素嵌套路径
-
再通过路径从inner自己到最外层元素执行事件冒泡
我们分别给各个元素绑一个冒泡和捕捉的事件来测下效果
javascript
document.addEventListener('click', () => {
console.log('document原生事件------冒泡');
});
document.addEventListener('click', () => {
console.log('document原生事件------捕获');
},true);
document.body.addEventListener('click', () => {
console.log('body原生事件------冒泡');
});
document.body.addEventListener('click', () => {
console.log('body原生事件------捕获');
}, true);
document.querySelector('.outer').addEventListener('click',
() => {
console.log('outer原生事件------冒泡');
});
document.querySelector('.outer').addEventListener('click',
() => {
console.log('outer原生事件------捕获');
}, true)
document.querySelector('.inner').addEventListener('click',
() => {
console.log('inner原生事件------冒泡');
});
document.querySelector('.inner').addEventListener('click',
() => {
console.log('inner原生事件------捕获');
}, true);
点击inner,打印结果:

从打印结果来看,与我们对事件流的理解是一致的
在这里可能大家会有个问题, 比如我只想执行inner上绑定的click方法,但不想执行outer以及body、document的绑定的click方法怎么办?
答案是可以的,我们可以通过阻止事件冒泡来实现。
我们在inner的click事件这里增加一个ev.stopPropagation()可以停止向外冒泡
javascript
document.querySelector('.inner').addEventListener('click',
(ev) => {
console.log('inner原生事件------冒泡');
ev.stopPropagation();
});
当我们点击inner的时候:
第一步. 从外到里执行每个元素的绑定的捕获click事件
第二步:从里到外,我们本应该执行每个元素绑定的冒泡的click事件,但在inner的绑定click事件里面我使用了ev.stopPropagation()阻止了继续向外冒泡,所以出现了以下打印的结果,执行到了inner的click就停止了。


讲到这里出一个思考题,如果我们给document的绑定捕获事件里面增加ev.stopPropagation()会发生什么呢?
javascript
document.addEventListener('click', (ev) => {
console.log('document原生事件------捕获');
ev.stopPropagation();
},true);
运行上面的代码会打印什么呢?大家可以按照事件流的理解在评论区留言,下一期公布答案。
事件委托
从刚刚这里例子来看,我们可以给每个dom元素绑定事件的方式来执行click,所以是不是每个dom元素我们需要手动去给他们绑定事件呢?大家想想,这样其实也挺麻烦,每次增加了dom元素后都需要手动绑一下,所以我们有个更方便的方法:事件委托(代理)
重点:事件委托其实是运用事件流的原理来实现的
刚刚我们也演示了从外到里捕获再到里到外冒泡的整个过程,拿click为例,无论点击了页面上任何一个元素在没阻止冒泡的情况下最终都会执行到最外层元素document绑定的click方法。我们可以试着这么写:
javascript
document.addEventListener('click', (ev) => {
console.log(ev.target);
});
分别点击inner与outer以及它们之外空白的地方,打印如下:

这里是在document绑定一个事件再通过ev.target可以返回点击当前dom元素的实例, 这是怎么做到的呢?前面我们讲过在事件捕获时记下了从最外层到当前元素的path,所以当点击任何一个元素冒泡到最外层元素时我们可以通过path找到对应的元素,而这对于js来说内部实现了并通过ev传递出来。
事件委托的好处:
- 动态
指定了一个父元素绑定事件后,后面增加增加子元素都会获取到这些子元素的实例。
- 方便
针对通用程序的处理非常方便,比如点击任何元素都获取到这个元素当前的一些信息并做通用的处理,比如给className为inner的元素做一个通用的逻辑
javascript
document.addEventListener('click', (ev) => {
const {target} = ev;
if(target && target.className === 'inner') {
console.log('------执行className为inner所有元素的通用的逻辑---------')
}
});
- 性能
dom元素渲染完后可以异步来获取dom的信息,比如点击的时候才去获取dom元素信息或者做相应的逻辑,而不需要在初始化的时候通过循环或者手动的方式给dom元素绑事件从而有效的减少了dom的访问次数。
javascript
const inners = document.querySelectorAll('.inner');
inners.forEach(inner => {
inner.addEventListener('click', function(ev) {
console.log(this)
});
});
虽然以上代码也能完成同样的需求,但是这意味着每次都会预先循环dom元素再绑定事件,这无疑会带来性能损耗。
使用事件委托需要注意的两点
不是所有的事件都能支持冒泡,从而支持事件委托
- 比如onfoucs, onblur,mouseleave、mouseenter 等等
- 当元素自绑定事件并且做了阻止冒泡处理的情况
总结:
今天介绍了原生事件机制:事件流与事件委托, 基于事件流的原理,事件委托在我们日常中广泛应用,比如埋点以及处理一些通用的事件处理,包括即将要讲的react事件底层机制里面也会说到事件委托,那本次我们就先讲到这里,大家记得本文中提到的思考题哦,欢迎回复评论, 更多文章欢迎关注公众号:大前端工程师