DOM事件模型浅析

1.何为DOM

DOM是“Document Object Model”的缩写,中文译为“文档对象模型”。它是一种跨平台、跨语言的编程接口,将HTML,XHTML,XML文档映射成树形结构,树的每一个节点都是一个对象。正因如此,面向对象的编程语言(如javascript)可以通过DOM对HTML,XHTML,XML文档进行操作。对于HTML文档来说,它的根结点为document对象,HTML元素为element对象,HTML元素的属性为attr对象。

2.何为DOM事件及如何对其作出响应

在浏览网页时,我们常常需要页面对用户的操作作出响应,比如点击“阅读全文”后我们期望页面展示被折叠的文本,按下回车键后浏览器提交已填好的表单。用户的各种操作都是“事件”。事件都是在对象上发生的,可能是DOM对象、BOM对象,等等。事件发生后,对象可能会作出响应,也有可能“无动于衷”。我们希望DOM元素对事件作出响应,一般而言有两种方法:
i.事件属性
事件属性是一种特殊的属性,它的值规定了对应事件发生时需要执行的javascript脚本。例:

<button onclick="console.log('button clicked!')"></button>

上面为button标签添加了事件属性onclick,其值为"console.log('button clicked!')",它规定了当元素被鼠标点击时,控制台输出'button clicked'。
ii.addEventListener()方法
EventTarget.addEventListener()方法将指定的监听器注册到EventTarget上,当该对象触发指定的事件时,指定的回调函数就会被执行。EventTarget可以是element对象,document对象或者任何其他支持事件的对象。例:

<!--html文件中-->
<button id='mybutton'></button>
//脚本中
var mybutton=document.getElementById('mybutton');
mybutton.addEventListener('click',function(e){console.log('button clicked!');});

上例为button元素注册了click事件的监听器,并规定事件时触发控制台输出'button clicked'。

3.DOM事件模型

在讲解DOM事件模型前,再用一个例子作为引入。请看下面的html文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>DOM Event Model</title>
    <style>
    div{position: absolute;}
    #outer{
        top: 100px;
        left: 100px;
        width: 600px;
        height: 400px;
        background-color: #aff;
    }
    #inner1,#inner2{
        top: 50px;
        width: 200px;
        height: 300px;
        background-color: #f9a;
    }
    #inner1{left: 50px;}
    #inner2{left: 350px;}
    #core{
        left: 50px;
        top: 50px;
        width: 100px;
        height: 150px;
        background-color: #f50;
    }
    </style>
</head>
<body>
    <div id='outer' onclick="console.log(this.id)">
        <div id='inner1' onclick="console.log(this.id)"></div>        
        <div id='inner2' onclick="console.log(this.id)">
            <div id='core' onclick="console.log(this.id)"></div>
        </div>
    </div>
</body>
</html>

这里为id分别为outer,inner1,inner2,core的4个元素定义了事件属性,元素被点击后将在控制台输出它的id。现在问题来了:
如果我点击core元素,控制台将会输出什么?
点击core元素时,由于core元素包含在inner2元素里,inner2元素同样被点击了;同理,inner2元素包含在outer元素里,那么outer元素也被点击了。这种情况下哪一个元素的click事件将会被触发,或者说三者都被触发?如果说三者都被触发,那么又是以怎样的顺序被触发?
我在火狐浏览器做了一次实验,控制台输出结果如下:

core
inner2
outer

也就是说,三者的事件都被触发了,且是“由内向外”触发的。
下面我们再做一个有趣的实验:我们将上面的html文件再做一个小小的改动,将core元素的样式

left: 50px;

改为

left: -250px;

此时观察页面我们会发现,尽量core是inner2的子节点,但由于我们定义了“怪异”的样式,它跑到了inner1里面。现在我们再次用鼠标点击core,观察控制台的输出:

core
inner2
outer

和刚才的结果一模一样!尽管表面上inner1似乎被点击了,但它的click事件并没有触发;反而是看似未被点击的inner2元素的click事件被触发了。仿佛core元素的click事件被触发后,click事件一层一层向上“传播”给了父节点。
为了解释刚才的实验结果,是时候开始讲解DOM事件模型了。
当一个事件发生时,事件会在DOM树中进行传播。传播分为两个阶段:
i.捕获阶段
在此阶段,事件从根结点(即document结点)开始向下传播,直到事件源所在元素。
ii.冒泡阶段
在此阶段,事件从事件源开始向上传播,直到根结点。
拿刚才的例子来说,事件传播的顺序为:
document捕获->html捕获->body捕获->outer捕获->inner2捕获->core捕获->core冒泡->inner2冒泡->outer冒泡->html冒泡->document冒泡
对于事件属性,默认在冒泡阶段触发事件。如果用addEventListener()方法注册监听器,则可以指定在捕获阶段还是冒泡阶段触发事件:如果最后一个参数为false(默认值),则在冒泡阶段触发事件;如果为true,则在捕获阶段触发事件。
一般来说,我们推荐采用addEventListener()方法来注册监听器,而尽量不用事件属性。因为事件属性不利于行为与结构的分离,使代码难以维护。

相关推荐