文档对象模型 DOM(Document Object Module)是由 W3C 定义的提供与任何 HTML 或 XML 文档进行交互的 API(编程接口),可以说是 HTML 之后的又一伟大创新。它使得用户可以通过 javaScript 访问 HTML 文档中的任意元素和内容,DOM 提供的接口可以操作 HTML 文件中的节点。当浏览器加载一个网页后,这个网页就可以看作是文档树,由多个节点构成。所谓 DOM,就是将 HTML 文档中各个元素按照从属关系建立起的模型。总的来说,我们可以利用 DOM 完成以下应用:
- 访问指定节点;
- 访问相关节点;
- 访问节点属性;
- 检查节点类型;
- 创建节点;
- 为节点添加事件;
- 操作节点。
HTML 文档与 DOM
我们先看下面这个简单的 HTML 文档:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>一个简单的 HTML 文档</title>
</head>
<body>
<h1>这是一个简单的 HTML 文档</h1>
<p>文档中包含着段落和<a title="link" href="#">超级链接</a></p>
</body>
</html>这个 HTML 文档的 DOM 可示例如下图:

在上面的 DOM 中,html 位于最顶端,是 DOM 的根节点,head 和 body 是 html 的子节点,它们属于同一层,并不互相包含,是兄弟关系,h1 和 p 是兄弟元素,其父元素是 body,p 的子元素是 a 元素。
节点
节点(node)的概念来源于计算机网络,它代表网络中的一个连接点。在 DOM 中,文档也是由节点构成的集合。DOM 定义了多种节点,常用的是元素节点、文本节点、和属性节点,分别对应元素、元素中包含的文字内容和元素的属性。
例如上面的代码中:
<a title="link" href="#">超级链接</a>a 元素的 title 和 href 属性就是 a 元素节点的属性节点,a 元素所包含的内容“超级链接”就是文本节点。如下图所示:
knitr::include_graphics('images/node.png')使用 DOM
DOM 提供了一种访问文档内容的机制,我们可以使用 DOM 处理 HTML 文档的内容。DOM 由节点(node)构成,每一个节点,都有一系列的属性、方法可以使用,常用属性、方法见下表:
| 属性/方法 | 类型/返回类型 | 说明 |
|---|---|---|
| nodeName | String | 节点名称 |
| nodeValue | String | 节点的值 |
| nodeType | Number | 节点类型 |
| firstChild | Node | childNodes 列表的第一个节点 |
| lastChild | Node | childNodes 列表的最后一个节点 |
| childNodes | NodeList | 所有子节点列表,方法 item(i) 可以访问其中节点 |
| parentNode | Node | 父节点 |
| previousSibling | Node | 指向前一个兄弟节点 |
| nextSibling | Node | 指向后一个兄弟节点 |
| hasChildNodes() | Boolean | 是否包含子节点 |
| attributes | NameNodeMap | 包含一个元素特性的 Attr 对象 |
| appendChild(node) | Node | 将 node 节点添加到 childNodes 末尾 |
| removeChild(node) | Node | 从 childNOdes 中删除 node 节点 |
| repaceChild(new,old) | Node | 将 childNodes 中的 oldnode 节点替换为 newnode 节点 |
| insertBefore(new,ref) | Node | 在 childNOdes 中的 refnode 节点之前插入 newnode 节点 |
| innerHTML | String | 读取或者设置某个标记之间的所有内容 |
| className | String | 读取或者设置节点的 CSS 类别 |
访问节点
DOM 提供了一些很便捷的方法来访问文档的节点,最常用的是 getElementsByTagName() 和getElementById(),此外比较常用的是 getElementsByName()、getElementsByClassName()、Document.querySelector()等等。
……
var oLi = document.getElementsByTagName('li');
for(var i = 0 in oLi) {
console.log(i + '=' + oLi[i]);
}
console.log(oLi);
console.log(oLi.length);
console.log(oLi[0].childNodes[0].nodeValue);
……将访问 oLi 子节点列表中的第一个节点的值。
检测节点类型
通过节点的 nodeType 属性可以检测出节点的类型,DOM 定义了 12 种类型,大多数情况下,我们用到的是以下类型:
ELEMENT_NODE元素节点 nodeType 的值为 1;ATTRIBUTE_NODE属性节点 nodeType 的值为 2;TEXT_NODE文本节点 nodeType 的值为 3;
利用父子兄关系查找节点
在获取了某个节点之后,可以通过父子关系,利用 hasChildNodes() 方法和 childNodes 属性获取该节点所包含的所有子节点。例如:
……
var oLi = document.getElementById('myList');
var DOMString = "";
if(oLi.hasChildNodes()){
var oChild = oLi.childNodes;
for(var i=0; i<oChild.length;i++){
DOMString += oChild[i].nodeName + "\n";
}
}
console.log(DOMString);
……从运行结果我们可以看出,不光有元素节点,连它们之间的空格也被当成了子节点。
通过 parentNode 属性,可以获取父元素节点。如:
……
var myItem = document.getElementById('active');
console.log(myItem.parentNode.tagName);
……DOM 还提供了处理兄弟之间关系的属性和方法,如 nextSibling、previousSibling。
设置节点属性
找到节点后,可以通过 getAttribute()、setAttribute() 方法取得或者设定节点的属性。
……
var myItem = document.getElementById('active');
console.log(myItem.parentNode.tagName);
console.log(myItem.getAttribute("title"));
myItem.setAttribute("title","主要用以和用户交互");
console.log(myItem.getAttribute("title"));
……创建和添加节点
创建元素节点采用 creatElment(), 创建文本节点采用 creatTextNode(), 创建文档碎片节点采用 creatDocumentFragment() 等等。
在插入元素之前,先将元素内容添加到元素节点,再将元素节点及其包含的文本节点添加到指定节点。
……
var oP = document.createElement("p");
var oText = document.createTextNode("这是一个使用 DOM 生成的段落");
oP.appendChild(oText);
document.body.appendChild(oP);
……删除节点
删除节点使用 removeChild() 方法,通常先找到要删除的节点,然后利用 parentNode 属性找到父节点,然后使用父节点的 removeChild 方法。
……
<body onload="removeLi()">
<ul id="myList">
<li>HTML</li>
<li>CSS</li>
<li id="active" title="JavaScript 是脚本编程语言">JavaScript</li>
</ul>
<script>
function removeLi() {
var oLi = document.getElementById('active');
oLi.parentNode.removeChild(oLi);
}
</script>
</body>
……替换节点
DOM 提供 replaceChild() 方法来替换节点,具体过程和删除节点类似,先找到想要替换的节点,再创建新节点,然后使用父节点替换方法。
……
<body onload="replaceLi()">
<ul id="myList">
<li>HTML</li>
<li>CSS</li>
<li id="active">JavaScript</li>
</ul>
<script>
function replaceLi() {
var oOldLi = document.getElementById('active');
var oNewLi = document.createElement('li');
var oText = document.createTextNode('ECMAScript');
oNewLi.appendChild(oText);
oOldLi.parentNode.replaceChild(oNewLi,oOldLi);
}
</script>
</body>
……innerHTML 属性
innerHTML 属性虽然不是 W3C DOM 的组成部分,但它得到了目前主流浏览器的支持。该属性表示某个标记之间的所有内容,包括代码本身。
var oLi = document.getElementById('active');
console.log(oLi.innerHTML);
oLi.innerHTML = "<a href='http://www.baidu.com'>Baidu</a>";className 属性
className 属性是一个非常实用的属性,可以修改一个节点的 CSS 类别。
var oLi = document.getElementById('active');
console.log(oLi.className);
oLi.className += ' jsdemo';
console.log(oLi.className);使用 className 属性如果要追加 CSS 样式,而不是覆盖 CSS 样式的话,使用“+=”连接运算符,另外注意不同的样式名称使用空格分割。