DOM事件机制,事件捕获与事件冒泡先后执行顺序
DOM事件流的三个阶段
流的概念,在现今的JavaScript中随处可见。比如说React中的单向数据流,Node中的流,又或是今天本文所讲的DOM事件流,都是流的一种生动体现。用术语说流是对输入输出设备的抽象。以程序的角度说,流是具有方向的数据。事件流所描述的就是从页面中接受事件的顺序。事件流也有两种,分别是事件冒泡和事件捕获。
在所有的现代浏览器当中——除了IE9之前的版本——都实现了DOM2标准事件模型,即事件处理过程的三个阶段:捕获,目标,冒泡;
-
捕获阶段
先由文档的根节点document往事件触发对象,从外向内捕获事件对象;当我们在 DOM 树的某个节点发生了一些操作(例如单击、鼠标移动上去),就会有一个事件发射过去。这个事件从 Window 发出,不断经过下级节点直到触发的目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。(所有经过的节点,都会触发这个事件。捕获阶段的任务就是建立这个事件传递路线,以便后面冒泡阶段顺着这条路线返回 Window。)在目标元素对象本身上注册的捕获事件处理程序不会被调用。
-
目标阶段
到达目标事件位置(事发地),触发事件;当事件不断的传递直到目标节点的时候,最终在目标节点上触发这个事件,就是目标阶段。
-
冒泡阶段
再从目标事件位置往文档的根节点方向回溯,从内向外冒泡事件对象;事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点(我们平时用的事件绑定就是利用的事件冒泡的原理)。
事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。
事件捕获的概念,与事件冒泡正好相反。它认为当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件
事件一开始从文档的根节点流向目标对象(捕获阶段),然后在目标对向上被触发(目标阶段),之后再回溯到文档的根节点(冒泡阶段)。
实例代码:
HTML:
<!DOCTYPE html> <meta name="robots" content="noindex"> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> </head> <body> <div class="level1 x"> <div class="level2 x"> <div class="level3 x"> <div class="level4 x"> <div class="level5 x"> <div class="level6 x"> <div class="level7 x"> </div> </div> </div> </div> </div> </div> </div> </body> </html>
CSS:
<style id="jsbin-css"> * { box-sizing: border-box; } div[class^=level] { border: 1px solid; border-radius: 50%; display: inline-flex; } .level1 { padding: 10px; background: purple; } .level2 { padding: 10px; background: blue; } .level3 { padding: 10px; background: cyan; } .level4 { padding: 10px; background: green; } .level5 { padding: 10px; background: yellow; } .level6 { padding: 10px; background: orange; } .level7 { width: 50px; height: 50px; border: 1px solid; background: red; border-radius: 50%; } .x{ background: transparent; } </style>
JS:
<script id="jsbin-javascript"> const level1 = document.querySelector('.level1') const level2 = document.querySelector('.level2') const level3 = document.querySelector('.level3') const level4 = document.querySelector('.level4') const level5 = document.querySelector('.level5') const level6 = document.querySelector('.level6') const level7 = document.querySelector('.level7') let n = 1 //注意e对象被传给所有监听函数 //事件结束后,e对象就不存在了 //所以我们先用一个变量保存e const bubblingPhase = e=>{ const t = e.currentTarget setTimeout(()=>{ t.classList.add('x') },n*1000) console.log('w'); n+=1 } const capturePhase = e=>{ const t = e.currentTarget setTimeout(()=>{ t.classList.remove('x') },n*1000) console.log('1'); n+=1 } level1.addEventListener('click',capturePhase,true) level1.addEventListener('click',bubblingPhase) level2.addEventListener('click', capturePhase,true) level2.addEventListener('click', bubblingPhase) level3.addEventListener('click', capturePhase,true) level3.addEventListener('click', bubblingPhase) level4.addEventListener('click', capturePhase,true) level4.addEventListener('click', bubblingPhase) level5.addEventListener('click', capturePhase,true) level5.addEventListener('click', bubblingPhase) level6.addEventListener('click', capturePhase,true) level6.addEventListener('click', bubblingPhase) level7.addEventListener('click', capturePhase,true) level7.addEventListener('click', bubblingPhase) </script>
运行代码:
当点击最里面(level7)的元素时,所有元素会先依次执行capturePhase(),再依次执行bubblingPhase(),可看到颜色变化.
特例:只有一个div被监听时(不考虑父子同时被监听),capturePhase()和bubblingPhase()分别在捕获阶段和冒泡阶段监听click事件,用户点击的元素就是开发者监听的.
addEventListener()第三个参数, 如果不传或为false就让fn走冒泡,如果为true,就让fn走捕获
stopPropagation() 方法:
事件冒泡过程,是可以被阻止的。防止事件冒泡而带来不必要的错误和困扰。
终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点。
一般用来封装某些独立的组件.
有些事件不可取消冒泡:
-
MDN搜索 scroll event ,看到Bubbles和Cancelable
-
Bubbles的意思是该事件是否冒泡
-
Cancelable的意思是开发者是否可以取消冒泡
-
阻止 scroll 滚动事件:
-
要阻止滚动,可阻止wheel和touchstart的默认动作
//阻止scroll默认动作没用,因先有滚动才有滚动事件 //鼠标滚动事件 api.addEventListener("wheel",(e)=>{ e.preventDefault(); }) //手机触摸事件 api.addEventListener("touchstart",(e)=>{ e.preventDefault(); })
-
但是滚动条还能用,可用CSS让滚动条width: 0
/*注意你需要找准滚动条所在的元素*/ ::-webkit-scrollbar{ width: 0; }
-
DOM事件中
target
和currentTarget
的区别target
是事件触发的真实元素(用户操作)currentTarget
是事件绑定的元素(开发者监听)- 事件处理函数中的
this
指向是中为currentTarget
currentTarget
和target
,有时候是同一个元素,有时候不是同一个元素 (因为事件冒泡)- 当事件是子元素触发时,
currentTarget
为绑定事件的元素,target
为子元素 - 当事件是元素自身触发时,
currentTarget
和target
为同一个元素。
- 当事件是子元素触发时,
-
绑定的元素(开发者监听)
- 事件处理函数中的
this
指向是中为currentTarget
currentTarget
和target
,有时候是同一个元素,有时候不是同一个元素 (因为事件冒泡)- 当事件是子元素触发时,
currentTarget
为绑定事件的元素,target
为子元素 - 当事件是元素自身触发时,
currentTarget
和target
为同一个元素。
- 当事件是子元素触发时,