原文地址:https://www.jianshu.com/p/dc1520327022
Js事件分发与DOM事件流
对JavaScript分发事件不熟悉,网上查阅相关资料整理后,记录一下对Javascript事件分发机制相关的知识。
当触发某个事件时会相应生成一个事件对象,而这个事件对象则会根据DOM事件流的方向进传递,看下图:
图片来源: http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
事件对象会随着DOM事件流从Window依次向下,最终传递给事件目标。但是在这个过程开始之前,事件对象的传递路径需要先被确定下来。
这个传递路径是一个有序的列表,里面包含了传递到事件目标需要经过的节点。而传递路径反映了文档的树结构。列表里面的最后一项就是事件目标,列表里面先于它的项指向目标的祖先节点,它的上一项指向目标的父节点。
比如上图由Window->Document->html->body->table->tr->td。一旦传递路径被确定了,事件对象就可以经历一个或者多个事件阶段。通常有三个阶段: 捕获阶段, 目标阶段, 冒泡阶段。某些阶段可能会被跳过,如果浏览器不支持,或者事件对象的传播被停止了。例如,如果把
cancelBubble
设置为
true
,冒泡阶段将会被跳过,或者
stopPropagation()
方法在传递之前就被调用的话,之后所有的阶段都会被跳过。
- 捕获阶段:事件对象从目标的祖先节点Window开始传播直至目标。
- 目标阶段:事件对象传递到事件目标。如果事件的type属性表明后面不会进行冒泡操作,那么事件到此就结束了。
- 冒泡阶段:事件对象以一个相反的方向进行传递,从目标开始,到Window对象结束。
直接上代码,写了三个嵌套的div:
<!DOCTYPE > <html> <head lang="en"> <title></title> <meta charset="UTF-8"> <style type="text/css"> #parent { width: 300px; height: 300px;padding:10px;background:lightyellow;} #child { width: 200px; height: 200px; background: lightblue; } #grandchild { width: 100px; height: 100px; background: lightcoral; } </style> </head> <body> <div id="parent"> parent <div id="child"> child <div id="grandchild"> grandchild </div> </div> </div> <script type="text/javascript"> window.alert = function (msg) { console.log(msg); }; var parent= document.getElementById('parent'), child = document.getElementById('child'), grandchild = document.getElementById('grandchild'); parent.addEventListener('click', function (e) { alert('父节点冒泡') }, false); parent.addEventListener('click', function (e) { alert('父节点捕获') }, true); child.addEventListener('click', function (e) { alert('子节点捕获') }, true); child.addEventListener('click', function (e) { alert('子节点冒泡') }, false); grandchild.addEventListener('click', function (e) { alert('孙子节点冒泡') }, false); grandchild.addEventListener('click', function (e) { alert('孙子节点捕获') }, true); </script> </body> </html>
当点击了parent时,依次打印:父节点冒泡,父节点捕获。
当点击了child时,依次打印:父节点捕获,子节点捕获,子节点冒泡,父节点冒泡。
当点击了grandchild时,依次打印:父节点捕获,子节点捕获,孙子节点冒泡,孙子节点捕获,子节点冒泡,父节点冒泡。
由此可以得出来结论,点击了目标节点后,捕获阶段里事件会从外向目标传递;到了目标阶段,捕获和冒泡的执行顺序按照事件被定义的先后顺序执行;最后冒泡阶段,又会由目标向外进行传递。
补充说明事件中stopPropagation
与stopImmediatePropagation
区别:
两都都可以阻止事件的进一步捕获或冒泡,但后者同时可以阻止任何事件处理程序被调用,包括目标阶段的事件。
比如修改上面代码里面子节点的目标事件:
child.addEventListener('click', function (e) { alert('子节点捕获') }, true); child.addEventListener('click', function (e) { e.stopPropagation(); // e.stopImmediatePropagation(); alert('子节点捕获2') }, true); child.addEventListener('click', function (e) { alert('子节点冒泡') }, false);
使用stopPropagation():
点击子节点,依次打印:父节点捕获,子节点捕获,子节点捕获2,子节点冒泡。
使用stopImmediatePropagation()
:点击子节点,依次打印:父节点捕获,子节点捕获,子节点捕获2。
两者都阻止了事件向父节点进行传播,同时使用stopImmediatePropagation()
阻止了事件向目标阶段后面的事件的传播。
参考:
https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
如果您在阅读过程中发现有什么问题,欢迎指正。