深入理解Javascript:DOM事件之事件传播

客户端Javascript程序采用异步事件驱动编程模型。

那首先什么是异步事件驱动编程模型呢?

在这种设计风格下,当文档、浏览器、元素或与之相关的对象发生某些有趣的事情时,Web浏览器就会产生事件。

这些对象等待事件发生,然后他们相应,这就是所谓的异步驱动风格。

1.事件是什么呢?

事件就是Web浏览器通知应用程序发生了什么事情。

事件类型:是一个用来说明发生什么类型事件的字符串;例如‘mousemove’表示用户移动鼠标,由于事件类型只是一个字符串,有时也可称为事件名字。

事件目标:发生的事件或者与之相关的对象。在JS应用程序中,Windows、Document、Element对象是最常见的事件目标。

下面的代码就是弹出事件目标与类型的方法:

<div id='box'>
    <button>按钮</button>
</div>
<script>
    var bt = document.getElementsByTagName('button')[0];

    bt.addEventListener('click',function(event){
        alert('事件目标:' + event.target);
        alert('事件类型:' + event.type);
    })
</script>

事件处理程序:处理或相应事件的函数。当在特定的事件目标上发生特定类型的事件时,浏览器会调用对应的事件处理程序。

事件对象:与特定事件相关且包含有关该事件详细信息的对象;例如,鼠标事件的相关对象会包含鼠标指针的坐标。

事件传播:浏览器决定哪个对象触发其事件处理程序的过程:

  • 对于单个对象的特定事件(比如windows的load事件),必须是不能传播的;
  • 事件传播向上传播,即冒泡传播;
  • 事件处理程序能够通过调用方法或者设置事件对象属性来阻止事件传播,这样就能停止冒泡;

事件捕获:事件传播的另外一种形式,在容器元素上注册的特定处理程序有机会在事件传播到真实目标之前捕获它。IE8或之前的版本不支持事件捕获,所以不常用它。

2.事件传播

上面介绍了什么是事件,下面详细介绍事件的传播机制;

当事件目标是Windows对象或其他的一些单独对象(如XMLHTTPRequest)时,浏览器简单的通过调用对象上适当的处理程序相应事件。

当事件目标是文档或者文档元素时,情况不同:

事件传播有三个阶段:

捕获阶段,发生在目标处理程序调用之前,称为‘捕获’阶段,为第一阶段

目标对象本身的时间处理程序调用是第二个阶段

事件冒泡是事件传播的第三个阶段

http://7xj29n.com1.z0.glb.clouddn.com/事件传播.jpg

3.事件冒泡

事件最开始由具体的元素(即事件发生的对象)接受,然后逐级向上传播至最不具体的那个节点。

下面是html:

<body>
    <div id='box'>
        <button>按钮</button>
    </div>
</body>

比如当点击上面HTML中的按钮,事件会一直向上冒泡,寻找注册了clink的事件处理程序的元素:

button --> box --> body --> html --> document root

4.事件捕获

事件捕获像反向的冒泡阶段;最先调用windows对象的捕获处理程序,然后是Document对象的捕获处理程序,接着是body对象的,再然后是DOM树向下,依次类推,直到调用事件目标的父元素的捕获事件处理程序。

事件捕获只能用于addEventListener()注册且第三个参数是true的事件处理程序中。意味着事件捕获无法在IE9之前的IE中使用。因为IE9之前的IE中不支持addEventListener。

<body>
    <div id='box'>
        <button>按钮</button>
    </div>
</body>

当点击上面html的按钮时,事件捕获阶段会从上到下寻找注册了click事件捕获处理程序的元素:

document root --> html --> body --> box --> button

5.事件的取消

取消事件的浏览器默认操作有三种方式:

  • 设置事件处理程序的返回值为false;
  • 在支持addEventListener的浏览器中,也能通过调用事件对象的preventDefault()方法取消事件的默认行为
  • 在IE9之前的IE中,可以设置事件对象的returnValue属性为false

取消事件的传播可以通过:

  • 在支持addEventListener的浏览器中,也能通过调用事件对象的一个stopPropagation()方法取消事件的继续传播;如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用stopPropagation方法之后其他任何对象的事件处理程序将不会调用
  • IE9之前不支持上一种方法的可以通过IE事件对象的cancelBubble属性设置为true能阻止事件进一步冒泡(这些版本事件传播不支持事件的捕获阶段)

6.综合例子

下面是一个综合例子:

当点击按钮时,会依次弹出对话框:‘事件捕获阶段body’、‘事件捕获阶段box’、‘事件捕获阶段:button’、‘事件调用阶段:button’、‘事件冒泡阶段:box’、‘事件冒泡阶段:body’;

当点击box时,会依次弹出对话框:‘事件捕获阶段body’、‘事件捕获阶段box’、‘事件冒泡阶段:box’、‘事件冒泡阶段:body’;

<html>
    <head>
        <title>事件流</title>
        <meta charset="utf-8">
        <style>
            #box {height:100px;width: 100px;border: solid 1px red; text-align: center;background-color: burlywood}
            #button {border: solid 1px red;}
        </style>
    </head>

    <body id="body">
        <div id="box">box
            <button id="button">按钮</button>
        </div>
        <script>
            var bt = document.getElementById('button');
            var bx = document.getElementById('box');
            var bd = document.getElementById('body');

            bt.addEventListener('click', function(){
                alert("事件捕获阶段:button");
            }, true);
            bx.addEventListener('click', function(){
                alert("事件捕获阶段box");
            },true);
            bd.addEventListener('click', function(){
                alert("事件捕获阶段body");
                // event.stopPropagation();
            },true);

            bt.addEventListener('click', function(){
                alert("事件调用阶段:button");
            }, false);
            bx.addEventListener('click', function(){
                alert("事件冒泡阶段:box");
            },false);
            bd.addEventListener('click', function(){
                alert("事件冒泡阶段:body");
            },false);
        </script>
    </body>

</html>