Js-事件分发与DOM事件流

原文地址:https://www.jianshu.com/p/dc1520327022

Js事件分发与DOM事件流

对JavaScript分发事件不熟悉,网上查阅相关资料整理后,记录一下对Javascript事件分发机制相关的知识。
当触发某个事件时会相应生成一个事件对象,而这个事件对象则会根据DOM事件流的方向进传递,看下图:

图为通过DOM事件流在DOM树传递事件的示意
图为通过DOM事件流在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时,依次打印:父节点捕获,子节点捕获,孙子节点冒泡,孙子节点捕获,子节点冒泡,父节点冒泡。
由此可以得出来结论,点击了目标节点后,捕获阶段里事件会从外向目标传递;到了目标阶段,捕获和冒泡的执行顺序按照事件被定义的先后顺序执行;最后冒泡阶段,又会由目标向外进行传递。

补充说明事件中stopPropagationstopImmediatePropagation区别:
两都都可以阻止事件的进一步捕获或冒泡,但后者同时可以阻止任何事件处理程序被调用,包括目标阶段的事件。
比如修改上面代码里面子节点的目标事件:

   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

如果您在阅读过程中发现有什么问题,欢迎指正。

原文地址:https://www.cnblogs.com/boonya/p/11165167.html