DOM事件流中的冒泡和捕获

bistukey 2019-06-21

这是本人的第一篇文章,欢迎大家多多批评指正!

DOM2级事件中addEventListener的执行机制,多个addEventListener同时添加时的执行先后规律:

W3C的DOM事件触发分为三个阶段:
1.事件捕获阶段:即由最顶层元素(一般是从window元素开始,有的浏览器是从document开始,至于其中的差别我稍后会更新)开始,逐次进入dom内部,最后到达目标元素,依次执行绑定在其上的事件;
2.处于目标阶段:检测机制到达目标元素,按事件注册顺序执行绑定在目标元素上的事件;
3.事件冒泡阶段:从目标元素出发,向外层元素冒泡,最后到达顶层(window或document),依次执行绑定再其上的事件。

在addEventListener中,利用第三个参数控制其是从哪个阶段开始,“true”是从捕获阶段开始,而“false”则是跳过捕获阶段,从冒泡阶段开始。

下面是一个简单的例子:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>事件流</title>
        <style type="text/css">
            #outer {
                width: 400px;
                height: 400px;
                background: #ccc;
            }
            #inner {
                width: 100px;
                height: 100px;
                background: #aaa;
            }
        </style>
    </head>
    <script type="text/javascript">
        window.onload = function() {
            var outer = document.getElementById("outer");
            var inner = document.getElementById('inner');

            outer.addEventListener("click", function() {
                alert("outer捕获");
            }, true);

            inner.addEventListener("click", function() {
                alert("inner捕获");
            }, true);

            outer.addEventListener("click",function() {
                alert("outer冒泡");
            }, false)
        }
    </script>
    <body>
        <div id="outer">
            <div id="inner"></div>
        </div>
    </body>
</html>

在这个例子里,如果我们点击内层元素inner,那么处于捕获阶段的“outer捕获”最先弹出,接下来是目标元素"inner捕获"弹出,最后是处于冒泡阶段的"outer冒泡"弹出,即:“outer捕获”-->"inner捕获"-->"outer冒泡"。 即使在代码里变换三个绑定事件的顺序,只要点击的是inner,这个执行顺序就不会变。

那么问题来了,如果我们点击的是外层outer的话呢?

要明白这个问题,我们必须明确一点:目标事件在哪一层,事件流就在哪一层回流,即使在outer事件下还有许多子孙节点,事件流都不会在outer之后往内流,此时,inner上的事件不会被触发,因此在这段代码中,只会弹出“outer捕获”和"outer冒泡"。

那么哪个先弹呢?由于此时事件处于第二阶段,即“处于目标阶段”,弹出顺序取决的也不再是捕获或冒泡,而是谁在代码中先注册,因此,在这段代码中,弹出的是:“outer捕获”→"outer冒泡"。

综上所述,事件在DOM中的执行顺序为:外层捕获事件→内层捕获事件→先注册的目标事件→后注册的目标事件→内层冒泡事件→外层冒泡事件

让我们加深理解,看下面这个例子:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>事件流</title>
        <style type="text/css">
            #box {
                width: 600px;
                height: 600px;
                background: yellow;
            }
            
            #outer {
                width: 400px;
                height: 400px;
                background: #ccc;
            }
            
            #inner {
                width: 100px;
                height: 100px;
                background: #aaa;
            }
        </style>
    </head>
    <script type="text/javascript">
        window.onload = function() {
            var outer = document.getElementById("outer");
            var inner = document.getElementById('inner');
            var oBox = document.getElementById('box');

            oBox.addEventListener("click", function() {
                alert('oBox冒泡');
            }, false)

            oBox.addEventListener("click", function() {
                alert('oBox捕获');
            }, true)

            outer.addEventListener("click", function() {
                alert("outer捕获");
            }, true);

            outer.addEventListener("click", function() {
                alert("outer冒泡");
            }, false);

            
            inner.addEventListener("click", function() {
                alert('inner冒泡')
            }, false);
            
            inner.addEventListener("click", function() {
                alert("inner捕获");
            }, true);
            /*综上所述,事件在DOM中的执行顺序为:外层捕获事件→内层捕获事件→先注册的目标事件→后注册的目标事件→内层冒泡事件→外层冒泡事件*/
        }
    </script>

    <body>
        <div id="box">
            <div id="outer">
                <div id="inner"></div>
            </div>
        </div>
    </body>

</html>

在这段代码里,点击inner,box上的捕获事件最先执行,然后是outer上的捕获事件,然后是inner上先注册的事件,然后是inner上后注册的事件,最后是box上的冒泡事件 弹出顺序为:'oBox捕获'→"outer捕获"→'inner冒泡'→"inner捕获"→"outer冒泡"→"oBox冒泡"。

补充一点,在ie8-中,由于addEventLister不起作用,我们使用attachEvent方法来绑定事件,此时在第二阶段,也就是处于目标阶段,如果目标元素上绑定了两个事件,那么其执行顺序和addEventLister相反:谁后注册谁先执行。

如果dom0级事件和dom2级事件同时存在,那执行顺序会是怎样呢?

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>事件流</title>
    <style>
        #box {
            width: 600px;
            height: 600px;
            background: yellow;
        }
            
        #outer {
            width: 400px;
            height: 400px;
            background: #ccc;
        }
            
        #inner {
            width: 100px;
            height: 100px;
            background: #aaa;
        }
    </style>
</head>
<body>
    <div id="box">
        <div id="outer">
            <div id="inner"></div>
        </div>
    </div>
</body>

<script type="text/javascript">
    window.onload = function () {

        var outer = document.getElementById("outer");
        var inner = document.getElementById('inner');
        var oBox = document.getElementById('box');

        oBox.addEventListener("click", function () {
            alert('oBox-Dom2冒泡');
        }, false);

        oBox.addEventListener("click", function () {
            alert('oBox-Dom2捕获');
        }, true);
        outer.onclick = function(){
            alert("outer-dom0 click! ");
        }
        outer.addEventListener("click", function () {
            alert("outer-Dom2冒泡");
        }, false);
        
         
        outer.addEventListener("click", function () {
            alert("outer-Dom2捕获");
        }, true);
    
        inner.addEventListener("click", function () {
            alert('inner-Dom2冒泡');
        }, false);

        inner.addEventListener("click", function () {
            alert("inner-Dom2捕获");
        }, true);
    }
</script>

上面的例子中,我将outer的div在捕获阶段和冒泡阶段都绑定了点击事件,同时还在绑定了dom0级的点击事件处理函数,这时如果我们点击inner,我们会发现,事件的执行顺序是这样的oBox-Dom2捕获--> outer-Dom2捕获 --> inner-Dom2冒泡 --> inner-Dom2捕获 -->outer-dom0 click!-->outer-Dom2冒泡 --> oBox-Dom2冒泡。

由此我们可以得出一个结论:当绑定dom0级事件元素不是目标元素时,那么绑定dom0级事件的处理是在冒泡阶段处理并按事件注册的先后顺序执行(W3C先注册的先执行) ,如果绑定dom0级事件的元素是目标元素时,则不论是捕获阶段绑定的处理函数还是冒泡阶段绑定的处理函数以及dom0级事件处理函数,他们的执行顺序都是按照注册的顺序执行(W3C先注册的先执行) 。

相关推荐