• 当一个元素嵌套了另一个元素,两个元素都对同一个事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。

    事件冒泡

    看下面这个例子:

        <div id="btn1">
            <div id="btn2">
                <div id="btn3">
                    hello world!
                </div>
            </div>
        </div>
        <script type="text/javascript">
            document.getElementById('btn1').addEventListener("click", function () {
                console.log("这是btn1")
            })
            document.getElementById('btn2').addEventListener("click", function () {
                console.log("这是btn2")
            })
            document.getElementById('btn3').addEventListener("click", function () {
                console.log("这是btn3")
            })
        </script>
    

    当hello world被点击时,控制台输出的信息是这样的:

    这些被按照自下而上的顺序依次触发,从子元素依次触发到父元素(直到document对象,有些浏览器则一直冒泡到window对象),这有点像在水中冒泡泡一样,从下面慢慢到上面,所以这叫做事件冒泡。

    阻止事件冒泡

    (1)、stopPropagation

    可以在触发函数中添加stopPropagation()方法来阻止事件冒泡,例如上面的代码中,我们只想让当hello World被点击时触发btn3的事件监听

        document.getElementById('btn3').addEventListener("click", function (event) {
            event.stopPropagation();
            console.log("这是btn3")
        })
    

    传入事件参数,触发stopPropagation()方法,然后看看控制台

    无论点击多少次都只会触发一个事件,即在遍历到第一个监听时就停止了遍历。

    如果添加到第二个(btn2)事件监听上,

        document.getElementById('btn2').addEventListener("click", function (event) {
            event.stopPropagation();
            console.log("这是btn2")
        })
    

    同样的,向上冒泡到第二个事件就停止了。

    (2)、cancelBubble

    顾名思义,取消冒泡,同样是上面那个例子,我们可以这样写

        document.getElementById('btn3').addEventListener("click", function (event) {
            event.cancelBubble = true;
            console.log("这是btn3")
        })
    

    同样也会达到只会触发btn3一个事件的效果

    (3)、cancelBubble和stopPropagation()的区别

    二者实现的效果是一样的,唯一的区别是在兼容性上面,cancelBubble的设计初衷是用在IE上面,下面是MDN文档中的兼容性列表

    以及在whatwg下的issue,cancelBubble legacy property · Issue #211 · whatwg/dom,有兴趣的可以去了解一下

    说白了是个历史遗留问题,貌似在除了Firefox之外的浏览器都能正常使用

    事件捕获

    同样是上面的代码,addEventListener()其实接受三个参数typelisteneruseCapture

    useCapture接受一个布尔值,默认为false,

    useCapturetrue时,事件只捕获,不冒泡。

        <div id="btn1">
            <div id="btn2">
                <div id="btn3">
                    hello world!
                </div>
            </div>
        </div>
        <script type="text/javascript">
            document.getElementById('btn1').addEventListener("click", function () {
                console.log("这是btn1")
            },true)
            document.getElementById('btn2').addEventListener("click", function () {
                console.log("这是btn2")
            },true)
            document.getElementById('btn3').addEventListener("click", function () {
                console.log("这是btn3")
            },true)
        </script>
    

    上面的代码点击时是这样的,事件从document对象(DOM2级规范规定,也有从window对象)一个个节点的遍历下来。

    IE8不支持useCapture,详情参考EventTarget.addEventListener() – Web API 接口

    因为老版本浏览器不支持,很少人会使用事件捕获,大部分情况下使用事件冒泡,有特殊情况下才会考虑使用事件捕获。