在网页中所有对象和内容都被称为节点,如文档、元素、文本、属性、注释等。节点(Node)是 DOM 最基本的单元,并派生出不同类型的节点,它们共同构成了文档的树形结构模型。
根据 DOM 规范,整个文档是一个文档节点,每个标签是一个元素节点,元素包含的文本是文本节点,元素的属性是一个属性节点,注释属于注释节点,以此类推。
DOM 支持的节点类型说明如表所示。
节点类型 | 说明 | 可包含的子节点类型 |
---|---|---|
Document | 表示整个文档,DOM 树的根节点 | Element(最多一个)、ProcessingInstruction、Comment、DocumentType |
DocumentFragment | 表示文档片段,轻量级的 Document 对象,仅包含部分文档 | ProcessingInstruction、Comment、Text、CDATASection、EntityReference |
DocumentType | 稳文档定义的实体提供接口 | None |
ProcessingInstruction | 表示处理指令 | None |
EntityReference | 表示实体引用元素 | ProcessingInstruction、Comment、Text、CDATASection、EntityReference |
Element | 表示元素 | Text、Comment、ProcessingInstruction、CDATASection、EntityReference |
Attr | 表示属性 | Text、EntityReference |
Text | 表示元素或属性中的文本内容 | None |
CDATASection | 表示文档中的 CDATA 区段,其包含的文本不会被解析器解析 | None |
Comment | 表示注释 | None |
Entity | 表示实体 | ProcessingInstruction、Comment、Text、CDATASection、EntityReference |
Notation | 表示在 DTD 中声明的符号 | None |
使用 nodeType 属性可以判断一个节点的类型,取值说明如表所示。
节点类型 | nodeType 返回值 | 常量名 |
---|---|---|
Element | 1 | ELEMENT_NODE |
Attr | 2 | ATTRIBUTE_NODE |
Text | 3 | TEXT_NODE |
CDATASection | 4 | CDATA_SECTION_NODE |
EntityReference | 5 | ENTITY_REFERENCE_NODE |
Entity | 6 | ENTITY_NODE |
ProcessingInstruction | 7 | PROCESSING_INSTRUCTION_NODE |
Comment | 8 | COMMENT_NODE |
Document | 9 | DOCUMENT_NODE |
DocumentType | 10 | DOCUMENT_TYPE_NODE |
DocumentFragment | 11 | DOCUMENT_FRAGMENT_NODE |
Notation | 12 | NOTATION_NODE |
下面示例演示如何借助节点 nodeType 属性检索当前文档中包含元素的个数。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>DOM</h1>
<p>DOM 是<cite>Document Object Model</cite>首字母简写,中文翻译为<b>文档对象模型</b>,是<i>W3C</i>组织推荐的处理可扩展标识语言的标准编程接口。</p>
<ul>
<li>D 表示文档,HTML 文档结构。</li>
<li>O 表示对象,文档结构的 JavaScript 脚本化映射。</li>
<li>M 表示模型,脚本与结构交互的方法和行为。</li>
</ul>
<script>
function count (n) { //定义文档元素统计函数
var num = 0; //初始化变量
if (n.nodeType == 1) //检查是否为元素节点
num ++; //如果是,则计数器加1
var son = n.childNodes; //获取所有子节点
for (var i = 0; i < son.length; i ++) { //循环统一每个子元素
num += count (son[i]); //递归操作
}
return num; //返回统计值
}
console.log("当前文档包含" + count (document) + "个元素"); //计算元素的总个数
</script>
</body>
</html>
演示效果如下:
在上面的 JavaScript 脚本中定义一个计数函数,然后通过递归调用的方式逐层检索 document 下所包含的全部节点,在计数函数中再通过 node.nodeType == 1 过滤掉非元素节点,进而统计文档中包含的全部元素个数。
使用 nodeName 和 nodeValue 属性可以读取节点的名称和值。属性取值说明如表所示。
节点类型 | nodeName 返回值 | nodeValue 返回值 |
---|---|---|
Document | #document | null |
DocumentFragment | #document-fragment | null |
DocumentType | doctype 名称 | null |
EntityReference | 实体引用名称 | null |
Element | 元素的名称(或标签名称) | null |
Attr | 属性的名称 | 属性的值 |
ProcessingInstruction | target | 节点的内容 |
Comment | #comment | 注释的文本 |
Text | #text | 节点的内容 |
CDATASection | #cdata-section | 节点的内容 |
Entity | 实体名称 | null |
Notation | 符号名称 | null |
通过上表可以看到,不同类型的节点,nodeName 和 nodeValue 属性取值不同。元素的 nodeName 属性返回值是标签名,而元素的 nodeValue 属性返回值为 null。因此在读取属性之前,应该先检测类型。
var node = document.getElementsByTagName ("body")[0];
if (node.nodeType == 1)
var value = node.nodeName;
console.log(value);
nodeName 属性在处理标签时比较实用,而 nodeValue 属性在处理文本信息时比较实用。
DOM 把文档视为一棵树形结构,也称为节点数。节点之间的关系包括:上下父子关系、相邻兄弟关系。简单描述如下:
针对下面这个 HTML 文档结构。
<! doctype html>
<html>
<head>
<title>标准 DOM 示例</title>
<meta charset="utf-8">
</head>
<body>
<h1>标准 DOM</h1>
<p>这是一份简单的<strong>文档对象模型</strong></p>
<ul>
<li>D 表示文档,DOM 的结构基础</li>
<li>O 表示对象,DOM 的对象基础</li>
<li>M 表示模型,DOM 的方法基础</li>
</ul>
</body>
</html>
在上面的 HTML 结构中,首先是 DOCTYPE 文档类型声明,然后是 html 元素,网页里所有元素都包含在这个元素里。从文档结构看,html 元素既没有父辈,也没有兄弟。如果用树来表示,这个 html 元素就是树根,代表整个文档。由 html 元素派生出 head 和 body 两个子元素,它们属于同一级别,且互不包含,可以称之为兄弟关系。head 和 body 元素拥有共同的父元素 html,同时它们又是其他元素的父元素,担保函的子元素不同。head 元素包含 title 元素,title 元素又包含文本节点“标准 DOM 示例”。body 元素包含 3 个子元素:h1、p 和 ul,它们是兄弟关系。如果继续访问,ul 元素也是一个父元素,它包含 3 个 li 子元素。整个文档如果使用树形结构表示,如下图所示。使用树形结构可以很直观的把文档结构中各个元素之间的关系表现出来。
DOM 为 Node 类型定义如下属性,以方便 JavaScript 访问节点。
childNodes 返回所有子节点的列表,它是一个随时可变的类数组。
下面示例演示了如何访问 childNodes 中的节点。
<ul>
<li>D 表示文档,HTML 文档结构。</li>
<li>O 表示对象,文档结构的 JavaScript 脚本化映射。</li>
<li>M 表示模型,脚本与结构交互的方法和行为。</li>
</ul>
<script>
var tag = document.getElementsByTagName ("ul")[0]; //获取列表元素
var a = tag.childNodes; //获取列表元素包含的所有子节点
console.log(a[0].nodeType); //第1个节点类型,返回值为3,显示为文本节点
console.log(a.item(1).innerHTML); //显示第2个节点包含的文本
console.log(a.length); //包含子节点个数,nodeList长度
</script>
使用中括号语法或者 item() 方法都可以访问 childNodes 包含的子元素。childNodes 的 length 属性可以动态返回子节点的个数,如果列表项目发生变化,length 属性值也会随之变化。
childNodes 是一个类数组,不能直接使用数组的方法,但是可以通过动态调用数组的方法把它转换为数组。下面示例把 childNodes 转换为数组,然后调用数组的 reverse() 方法颠倒数组中元素的顺序。
var tag = document.getElementsByTagName ("ul")[0]; //获取列表元素
var a = Array.prototype.slice.call (tag.childNodes, 0); //把childNodes属性值转换为数组
a.reverse(); //颠倒数组中元素的顺序
console.log(a[0].nodeType); //第1个节点类型,返回值为3,显示为文本节点
console.log(a[1].innerHTML); //显示第2个节点包含的文本
console.log(a.length); //包含子节点个数,childNodes属性值长度
演示效果如下:
文本节点和属性节点都不包含任何子节点,所以它们的 childNodes 属性返回值是一个空集合。可以使用 haschildNodes() 方法或者 childNodes.length>0了来判断一个节点是否包含子节点。
parentNode 返回元素类型的父节点,因为只有元素才可能包含子节点。不过 document 节点没有父节点,document 节点的 parentNode 属性将返回 null。
firstChild 返回第一个子节点,lastChild 返回最后一个子节点。文本节点和属性节点的 firstChild 和 lastChild 属性返回值为 null。
firstChild 等价于 childNodes 的第一个元素,lastChild 属性值等价于 childNodes 的最后一个元素。如果 firstChild 等于 null,这说明当前节点为空节点,不包含任何内容。
nextSibling 返回下一个相邻节点,previousSibling 返回上一个相邻节点。如果没有同属一个父节点的相邻节点,则返回 null。
ownerDocument 表示根节点。node.ownerDocument 等价于 document.documentElement。
针对下面文档结构。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body><span class="red">body</span>元素</body></html>
可以使用下面方法访问 body 元素。
var b = document.documentElement.lastChild;
var b = document.documentElement.firstChild.nextSibling.nextSibling;
通过下面方法可以访问 span 包含的文本。
var text = document.documentElement.lastChild.firstChild.firstChild.nodeValue;
操作节点的基本方法如表所示。
方法 | 说明 |
---|---|
appendChild() | 向节点的子节点列表的结尾添加新的子节点 |
cloneNode() | 复制节点 |
hasChildNodes() | 判断当前节点是否拥有子节点 |
insertBefore() | 在指定的子节点前插入新的子节点 |
normalize() | 合并相邻的 Text 节点并删除空的 Text 节点 |
removeChild() | 删除(并返回)当前节点的指定子节点 |
replaceChild() | 用新节点替换一个子节点 |
appendChild()、insertBefore()、removeChild()、replaceChild() 四个方法用于对子节点进行操作。使用这四个方法之前,可以使用 parentNode 属性先获取父节点。另外,并不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用了这些方法将会导致错误发生。
下面示例为列表框绑定一个 click 事件处理程序,通过深度克隆,新的列表框没有添加 JavaScript 事件,仅克隆了 HTML 类样式和 style 属性。
<h1>DOM</h1>
<p>DOM 是<cite>Document Object Model</cite>首字母简写,中文翻译为<b>文档对象模型</b>,是<i>W3C</i>组织推荐的处理可扩展标识语言的标准编程接口。</p>
<ul>
<li class="red">D 表示文档,HTML 文档结构。</li>
<li title="列表项目2">O 表示对象,文档结构的 JavaScript 脚本化映射。</li>
<li style="color:red;">M 表示模型,脚本与结构交互的方法和行为。</li>
</ul>
<script>
var ul = document.getElementsByTagName ("ul")[0]; //获取列表元素
ul.onclick = function () { //绑定事件处理程序
this.style.border = "solid blue 1px";
}
var ul1 = ul.cloneNode (true); //深度克隆
document.body.appendChild (ul1); //添加到文档树中 body 元素下
</script>
演示效果如下: