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()方法来注册监听器,而尽量不用事件属性。因为事件属性不利于行为与结构的分离,使代码难以维护。