当前位置:嗨网首页>书籍在线阅读

12-事件捕获与事件冒泡

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

18.11 事件捕获与事件冒泡

由于HTML是层级结构,这使得可以在多个地方处理事件。比如,当一个button被点击时,不仅button本身可以处理这个点击事件,它的父亲、父亲的父亲,也可以处理这个事件。那么问题来了,“这些元素是按照什么顺序来处理事件呢?”

这里有两种可能性。一个是从最外层的祖先节点开始,称为事件捕获(capturing)。在本例中,botton是 <div id="content"> 的孩子节点,而它又是的孩子节点。因此,最先“捕获”到最终会达到button的事件。

另一个选择是从触发事件的元素开始,然后按照HTML的层级结构向上走,这样所有的祖先都有机会处理事件。这称作事件冒泡(bubbling)。

为了支持这两种方式,在HTML5中,事件的传播是从处理器捕获事件开始(从最外层的祖先节点,一直到目标元素),然后才是事件冒泡,也就是从目标元素到最外层的祖先节点。

任何一个处理器都可以通过以下三件事中的一件来影响(或阻止)其他处理器的调用。第一件同时也是最常见的,是已经见过的 preventDefault ,它可以取消事件。取消的事件会继续传播,但是它们的 defaultPrevented 属性会被置成true。浏览器内置的事件处理器都会遵从 defaultPrevented 属性,不执行任何操作。而自己编写的事件处理器则可以(通常也会这么做)选择性地忽略这个属性。第二件事是调用stopPropagation,时间传播到当前元素之后,将不会继续传播(所有绑定到当前元素上的处理器都会被调用,但绑定到其他元素上的处理器不会被调用)。最后一件事,也是重头戏:调用 stopImmediatePropagation 会阻止所有还未调用的处理器被调用(即使这些处理器被绑定在当前元素上)。

为了演示这些功能,我们来看看下面的HTML:

<!doctype html>
<html>
    <head>
        <title>Event Propagation</title>
        <meta charset="utf-8">
    </head>
    <body> 
        <div> 
            <button>Click Me!</button>
        </div>
        <script> 
            // 创建一个事件处理器并返回这个处理器
            function logEvent(handlerName, type, cancel,
                    stop, stopImmediate) {
                // 这才是真正的事件处理器
                return function(evt) {
                    if(cancel) evt.preventDefault();
                    if(stop) evt.stopPropagation();
                    if(stopImmediate) evt.stopImmediatePropagation();
                    console.log(`${type}: ${handlerName}` +
                        (evt.defaultPrevented ? ' (canceled)' : ''));
                } 
            } 
            // 给元素上添加一个logger事件
            function addEventLogger(elt, type, action) {
                const capture = type === 'capture';
                elt.addEventListener('click',
                    logEvent(elt.tagName, type, action==='cancel',
                    action==='stop', action==='stop!'), capture);
            } 
            const body = document.querySelector('body');
            const div = document.querySelector('div');
            const button = document.querySelector('button');
            addEventLogger(body, 'capture');
            addEventLogger(body, 'bubble');
            addEventLogger(div, 'capture');
            addEventLogger(div, 'bubble');
            addEventLogger(button, 'capture');
            addEventLogger(button, 'bubble');
        </script>
    </body>
</html>     

点击button,会在console中打印以下内容:

capture: BODY
capture: DIV
capture: BUTTON
bubble: BUTTON
bubble: DIV
bubble: BODY

从这个例子中可以清楚地看到事件传播的顺序:先是事件捕获,然后是事件冒泡。注意,在产生事件的元素上,处理器被调用的顺序就是它们被添加的顺序,不论它们是捕获事件还是传播事件(如果把捕获处理器和冒泡处理器的添加顺序颠倒一下,那冒泡处理器将会在捕获处理器之前被调用)。

下面来看一下如果取消事件传播会发生什么。修改前面的例子,取消 <div> 上的捕获事件传播。

addEventLogger(body, 'capture');
addEventLogger(body, 'bubble');
addEventLogger(div, 'capture', 'cancel');
addEventLogger(div, 'bubble');
addEventLogger(button, 'capture');
addEventLogger(button, 'bubble');

可以看到传播还在继续,但是事件已经被标记为取消:

capture: BODY
capture: DIV (canceled)
capture: BUTTON (canceled)
bubble: BUTTON (canceled)
bubble: DIV (canceled)
bubble: BODY (canceled)

现在停止