01期:web浏览器事件传播机制(捕获和冒泡)

今天想跟大家分享一下我对web事件传播机制的理解。


一、背景


在实际开发过程中,我们有时会给多个具有嵌套关系的dom元素,绑定类型相同但处理不同的event listener,如click listener。当内层元素绑定的click事件被触发时,其外层元素的click事件也会被触发,那么请思考一下,如何控制它们的执行顺序?


二、web标准定义的listener执行顺序


在确定执行顺序之前,我们先看一下存在哪几种可能的执行顺序。从dom元素的嵌套关系来看:

我们可以看到两种基本顺序,一是从“外到内”,二是“从内到外”。而web标准采用的顺序是“先从外到内,再从内到外”,是这两种基本顺序的拼接。刚好是一个来回一个往返,我喜欢把这种顺序定义为“公交路线”。


特别地,

1. 每一个嵌套的dom元素都可以看成是“公交路线”上的一个巴士站,,每一个巴士站都会经停两次,第一次是巴士出站时,第二次是巴士回站时(ps:bus总站为最外层元素,终点站为最内层元素)


2. 每一个绑定在dom元素的listener可以看作是一个乘客,同一个dom元素可能绑定多个listener,意味着有个乘客队列,遵循“先上车先执行,上车即执行”原则(ps:此原则同样适用于具有嵌套关系的多个dom元素间的listeners);


三、两种基本顺序的命名


在谈命名前,我先描述一下“陨石坠落到地球及其事件上报”的过程:


打个比喻,太空有一颗巨大的陨石进入地球大气层,经过5小时,到达了中国领空(具体坠落到何处尚不能知晓);再经过5小时,坠落轨迹已经稳定在广东省境内;再经过5小时,轨迹稳定在深圳市;再经过30分钟,轨迹稳定在南山区;再经过10分钟,陨石最终坠落在桂庙村。桂庙村村委会在查看现场后,立即把事件的详情上报到南山区政府,而南山区政府接着把事件上报给深圳市政府,而深圳市把又接着把事件上报给省级单位......经过一级一级的上报,最后,中央政府也知悉此事件。


这个过程,可细分为有三个过程:

1. 陨石坠落过程(go down),也是从外到内的过程;

2. 陨石到达地球桂庙村(reach target);

3. 事件详情逐层上报(bubble up冒泡上升),也是从内到外的过程。

 

过程讲完了,我们回到对两种基本顺序命名的问题。通过查阅资料可知,官方把“从外到内”的顺序命名为Capturing,“从内到外”的顺序命名为Bubbling。结合刚描述完的“陨石坠落到地球及其事件上报”过程,将“从内到外”理解为Bubbling问题不大,因为Bubbling正好对应之前提及的bubble up(上报)。而将“从外到内”与Capturing直接联系在一起,就似乎有那么一点困难。


我想你我也有过同样的困惑。而后来我是通过这样来理解:回到我之前的陨石坠落的比喻,在陨石坠落的过程中,我们是无法最终确定其将reach到地表何处,但是我们可以确定的是,陨石先进入地球(先被地球知晓),再进入中国领空(再被中国知晓),后进入深圳领空(后被深圳知晓)......最终到达桂庙村(最终被桂庙村知晓)。


从上述过程可知,“知晓”先始于从外层,终于里层,而欲“捕获”则应先“知晓”,可知“捕获”也是先始于从外层,终于里层。这样就不难理解为何把“从外到内”的顺序命名为Capturing。


四、如何设置事件监听器在哪个基本顺序执行(Capturing or Bubbling)    


先看看官方提供的给元素增加事件监听器的接口:

element.addEventListener(type, listener[, useCapture]);


其中的useCapture(使用捕获)参数,就是用于设置当前listener在哪个基本顺序执行:当检测到type事件触发时,useCapture为true,表示会在“公交路线”的“Capturing”方向搭载(执行)当前listener;为false,表示会在“公交路线”的“Bubbling”方向搭载(执行)当前listener。 


再来讨论一下useCapture的默认值,可以思考一下为什么是false,而是true?按我的理解,false更符合一般的认知规律:就好比陨石落到地表之后,事件传播的一般顺序是由下级区域上报(bubble up)给上级区域,上级区域是被动知道事件的发生。相应地,外层元素好比上级区域,内层元素好比下级区域,也可以认为在默认情况下,外层元素捕获到的事件也是源于内层元素的上报。不知道这种解释有没有帮助你记忆?


另外,我想补全一下useCapture中的capture语义来帮助理解:The dom element captures the event and does sth in the relevant listener. 在读完之后,我们不难从句子中读到一种主动的蕴味。也就是说,useCapture为true时,表示dom元素是主动捕获事件(而不是被下层元素通知事件);相对地,为false时, 表示dom元素是被动知道事件(由下层元素通知)。因为如之前所说,由下层元素上报而知道事件的发生,更符合一般的事件传播顺序,所以setCapture设为false更具有普遍性,设为默认值也更为合理。


五、事件监听器开始执行的前置条件:事件对象已初始化完毕(target已确定)


我们在网上经常会看到这样的现象(我以前也有这样理解):就是认为listeners的执行是穿插在浏览器的事件解析、捕获和传播过程(包含事件对象初始化),举例如下:


html结构:


js代码(listener的useCapture均设为true,让其在元素捕获到事件时执行):


实际结果:

所有的target均指向最里层的触发了事件的dom元素,即target.id均为station4。



其实web标准定义的事件执行顺序,与浏览器的事件解析、捕获和传播过程(包含事件对象初始化),是两个无交叉的过程。二者之间的关系仅仅是官方在给事件基本执行顺序命名的时候,借用了后者的术语Capture和Bubble(把“从外到内”的顺序命名为Capturing,“从内到外”的顺序命名为Bubbling)。而事实上,listeners的执行,是在“事件对象生成并初始化完毕”之后,从上述的“实际结果”可知(详见代码e.target.html)。最后再强调几点:

1. 事件对象的初始化,包括event.target的确定(对应最内层的dom元素);

2. 在event.target确定之后,事件触发的相关listeners方开始执行;

3. 由同一个用户行为触发执行的listeners, 无论其实是在Capturing方向还是Bubbling方向,其event target均一样。



六、从代码中理解事件传播机制


讲完概念之后,是时候上代码了!

思来想去,时间也不早了,不想把代码和要点重新贴一遍~代码已放到本人github上,内含丰富注释,相关要点亦详细罗列,建议clone。以下是项目地址:https://github.com/momopig/simplicity/tree/master/01:event_propagation


七、结束语

web事件传播机制的要点之一,就是为背景所提出的问题:如何控制具有嵌套关系且绑定了相同类型事件监听器的多个元素间的listeners的执行顺序,提供了一套解决方案。方案要点有:

1. 定义了“先从外到内,再从内到外”的执行顺序;

2. dom元素的addEventListener方法,使用useCapture来控制当前绑定的listener是在“从外到内”的方向上被执行,还是在“从内到外”的方向上被执行;


关于文中提到的第五点,由于没有深入研究过浏览器的事件解析、捕获和传播过程,所以难免会有纰漏或措辞不准确的地方,还望见谅。如果你发现了我的问题,或者有更深入的了解,还望不吝赐教。





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值