1.0 关于DOM

简单介绍

DOM(全称为Document Object Model)即文档对象模型是用于表示和操作HTML或XML文档内容的一套基础API。


✧ 有时候我们可能会看到DHTML这个专业术语:DHTML是动画HTML的简称,其并不是一项新的技术,而是描述HTML CSS JavaScript技术组合的术语。它曾被认为是HTML/XHTML CSS和JavaScript相结合的产物,像今天的HTML5,但真正凝聚它们的是DOM。

当网页被加载时,浏览器会内部的引擎会根据DOM模型,将结构化文档(比如HTML和XML)解析成一系列的节点,再由这些节点构建出一种树状结构(DOM Tree)。

下面给出一段简单的HTML示例代码和对应的DOM树结构图。图示中的的方框代表着文档中的一个个节点,每个方框(节点)暨一个Node对象,所有这些节点组成了DOM树。

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM树演示代码</title>
</head>
<body>
<div class="className">Hi! 文顶顶</div>
<a href="http://www.wendingding.com"></a>
</body>
</html>

节点的类型

HTML页面中拥有众多类型的节点,不同类型的节点其表示和操作的方式有很大的差异,下面分别列出:

❏ Text:标签之间或标签包含的文本内容
❏ Comment:HTML或XML中的注释
❏ Element:网页的各种HTML标签,如a标签 div标签等
❏ Document:整个DOM树的根,代表整个文档
❏ Attribute:网页元素的属性节点
❏ DocumentType:文档类型(doctype)标签
❏ DocumentFragment:文档的片段,如果感觉费解请移步MDN-DocumentFragment

尽管在HTML页面中存在着如此众多类型的节点,但我们真正需要关注的主要还是:元素节点属性节点文本节点。在(HTML|XHTML)文档中,文本节点总是被包含在元素节点的内部,属性节点用来对元素做出更具体的描述。

1
2
3
4
在上面的图示中,我们提供了一个div标签,该标签拥有“上坡和下坡不就是同一条路吗”文本内容和两个属性节点。
① 个div标签由开始标签和结尾标签组成,本身是Element类型的。
② “上坡和下坡不就是同一条路吗”作为div标签的文本内容,本身是Text类型的。
③ div标签中的class和title是属性节点(key-value),本身是Attribute类型的。

节点关系

DOM中节点的关系主要有以下几种情况

子节点 父节点 后代节点(子孙节点) 祖先节点 兄弟节点

2.0 Node && Elemet && nodeType

Node(节点)和 Element(元素节点)是严格区分的。也就是说Node和Element不能简单的混为一谈,因为很多人都搞不清楚它们的关系,所以这里单独拿出来讨论。

Node 节点,表示构成DOM树的最小组成部分。换句话来说,在页面中不论是元素节点、文本节点还是注释或者别的东西本质上都是Node节点。

Element元素节点,是Node节点中的一种类型。

通俗的来讲,node节点就像人一样,是一种基本的类型。(大哲学家柏拉图对人的定义是:人是两腿无毛会直立行走的动物 :) 而人这种基本类型中,又存在着小孩、中年人、老年人、学生、教师、司机、男人、女人等种种具体的类型。

对应到这里的关系,那么Element其实是node的一种更具体的类型。不止Element,像Text、Comment以及Attribute等等这些其实都是特殊的Node,它们拥有自己的类型常量(TEXT_NODE、COMMENT_NODE以及ATTRIBUTE_NODE)用于区分彼此。

文档中所有的node节点都拥有nodeType属性,我们可以通过该属性的值来确定节点的具体类型,下面列出对应关系(标红的常量表示在DOM4中被废弃)。

1
2
3
4
5
6
# 可以直接在开发者工具的控制台中像下面这样检测和验证节点的类型
document.body.nodeType //输出结果为1
document.body.ELEMENT_NODE //输出结果为1
document.body.ELEMENT_NODE == document.body.nodeType //输出结果为true
# 需要注意的是ELEMENT_NODE是常量

NodeList 和 HTMLCollection类型

相信很多开发者都有这样的经验,“我们通过节点的childNodes属性获取的结果和children属性获取的结果是不一样的”。下面我们通过一段简短的代码来说明它们的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
<div id="demoID">
我是测试的文字---A!!
<div>div1</div>
<div>div2</div>
<div class="className">div3</div>
<!--注释的内容:后面跟span标签-->
<span>我是span</span>
我是测试的文字---B!!
</div>
<script>
var oDiv = document.getElementById("demoID");
console.log("元素节点的children属性 == HTMLCollection类型");
console.log(oDiv.children);
console.log(oDiv.children.length);
console.log("元素节点的childNodes属性 == NodeList类型");
console.log(oDiv.childNodes);
console.log(oDiv.childNodes.length);
</script>
...

通过代码的执行情况可以发现,元素节点(这里为id为demoID的div元素)的children属性得到的是HTMLCollection类型的伪数组,而childNodes属性得到的是NodeList型的伪数组。[注意:它们是伪数组的结构,可以遍历但非真正意义上的数组]。

NodeList是Node集合,而HTMLCollection可以认为是Element的集合。

通常来说Document和HTMLDocument以及Element类型与和HTMLElement类型是严格区分的。Document类型代表一个HTML或XML文档,Element类型代表该文档中的一个元素。而HTMLDocument和HTMLElement通常只针对HTML文档和其元素。

3.0 DOM操作基础

Node(节点)的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
------------------------
#### Node节点的主要属性
------------------------
baseURI // 当前网页的绝对路径
children // 指定节点的所有Element子节点
childNodes // 当前节点的所有子节点
childElementCount // 当前节点所有Element子节点的数量
nodeName // 节点名称[只读]
nodeType // 节点类型的常数值[只读]
nodeValue // Text或Comment节点的文本值[只读]
innerText // 节点的文本内容
nextSibling // 紧跟在当前节点后面的第一个兄弟节点
isConnected // 布尔类型的值,用于检查当前节点与DOM树是否连接[只读]
textContent // 当前节点和它的所有后代节点的文本内容
ownerDocument // 当前节点所在的顶层文档对象,即Document
previousSibling // 当前节点的前一个兄弟节点
parentNode // 当前节点的父节点
parentElement // 当前节点的父Element节点
firstChild // 当前节点的第一个子节点
firstElementChild // 当前节点的第一个Element子节点
lastChild // 当前节点的最后一个子节点
lastElementChild // 当前节点的最后一个Element子节点
------------------------
#### Node节点的主要方法
------------------------
cloneNode(true); // 克隆节点,参数传递布尔类型的值(默认为false)
hasChildNodes() // 布尔类型的值,表示当前节点是否有子节点
appendChild(node) // 追加新的节点到当前节点中(插入后作为最后一个节点)
removeChild(node) // 删除指定的子节点
isEqualNode(noe) // 布尔类型的值,用于检查两个节点是否(完全)相等
contains(node) // 布尔类型的值,用于判断参数节点是否为当前节点的后代节点
normalize() // 清理当前节点内的所有Text节点,将去除空的文本节点并合并文本。
insertBefore(newNode,oldNode) // 在指定子节点之前插入新的子节点
replaceChild(newChild,oldChild) // 替换节点
compareDocumentPosition(node) // 比较当前节点与指定节点的位置关系,返回不同的掩码。
------------------------
#### ChildNode相关的方法
------------------------
ChildNode.replace() // 替换节点
ChildNode.remove() // 将ChildNode从其父节点的子节点列表中移除
ChildNode.before() // 在当前标签(节点)的前面插入新的节点
ChildNode.after() // 在当前标签(节点)的后面插入新的节点

Element(元素节点)的属性和方法

元素节点继承了Node的所有属性和方法,Element本身也作为通用的基类来使用。
下面列出元素节点的主要属性和方法,如果没有特别标注为[读写]的,那么默认为只读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
------------------------
#### Element主要的属性
------------------------
id // 指定元素的id属性[读写]
attributes // 指定元素节点相关的所有属性集合(映射)
tagName // 指定元素节点的标签名(大写)
innerHTML // 指定元素节点包含的节点内容[读写],区别于innerText
outerHTML // 指定元素节点的所有HTML代码,包括它自身和包含的的所有子元素[读写]
className // 元素节点的class属性值[读写]
classList // 元素节点所有的class属性。
dataset // 元素节点中所有的data-*属性。
localName // 元素名称本地化的结果
clientTop // 元素节点距离它顶部边界的高度
clientLeft // 元素节点距离它左边界的宽度
clientHeight // 元素节点内部(相对于外层元素)的高度
clientWidth // 元素节点内部(相对于外层元素)的宽度
style // 元素节点的行内样式
scrollHeight // 元素节点滚动视图的高度
scrollWidth // 元素节点滚动视图的宽度
scrollLeft // 元素节点横向滚动条距离左端的位移[读写]
scrollTop // 元素节点纵向滚动条距离顶部的位移[读写]
offsetHeight // 元素节点相对于版面或由父坐标 offsetParent 属性指定的父坐标的高度
offsetWidth // 元素节点相对于版面或由父坐标 offsetParent 属性指定的父坐标的宽度
offsetLeft // 元素节点相对于版面或由父坐标 offsetParent 属性指定的父坐标的左侧位置位移
offsetTop // 元素节点相对于版面或由父坐标 offsetParent 属性指定的父坐标的顶部位置位移
firstElementChild // 获取元素节点的第一个子元素
lastElementChild // 获取元素节点的最后一个子元素
nextElementSibling // 获取元素节点的下一个兄弟节点
previousElementSibling // 获取元素节点的上一个兄弟节点
------------------------
#### Element主要的方法
------------------------
[⦿] 操作属性节点相关的方法
getAttribute() // 读取指定属性
setAttribute() // 设置指定属性
hasAttribute() // 返回布尔类型的值,检查当前元素节点中是否有指定的属性
removeAttribute() // 移除指定属性
[⦿] 选择器相关方法
querySelector() // 根据参数获取选中的所有标签中的第一个返回
querySelectorAll() // 根据参数获取选中的所有标签返回
getElementsByTagName() // 根据标签名来获取指定的标签返回
getElementsByClassName() // 根据class的名称来获取指定的标签返回
[⦿] 事件相关方法
addEventListener() // 添加事件监听的回调函数
removeEventListener() // 移除事件监听函数
dispatchEvent() // 触发事件
attachEvent() // 添加事件监听的回调函数(IE 9-)
detachEvent() // 移除事件监听函数(IE 9-)
insertAdjacentHTML() // 指定的文本解析为HTML或XML,并将结果节点插入到DOM树中的指定位置

Document(文档)的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
------------------------
#### Document的主要属性
------------------------
document.body // 当前文档的<body>节点
document.head // 当前文档的<head>节点
document.defaultView // 当前文档对象所在的window对象
document.doctype // 当前文档关联的文档类型定义(DTD)
document.documentElement // 当前文档的根节点
document.activeElement // 当前文档中获得焦点的那个元素。
document.links // 当前文档的所有连接集合
document.forms // 页面中所有表单元素的集合
document.images // 页面中所有图片元素的集合
document.embeds // 网页中所有嵌入对象的集合
document.scripts // 当前文档的所有脚本的集合
document.styleSheets // 当前网页的所有样式表的集合
document.URL // 当前文档的URL地址
document.cookie // 用来操作Cookie[读写]
document.title // 当前文档的标题
document.domain // 当前文档的域名
document.referrer // 当前文档的访问来源
document.location // 获取location对象,提供与UR相关的L信息
document.readyState // 当前文档的状态
document.designMode // 控制当前文档是否可编辑[读写]
document.compatMode // 返回浏览器处理文档的模式
document.documentURI // 当前文档的URI地址
document.lastModified // 当前文档最后修改的时间戳
document.characterSet // 当前文档的字符集,比如UTF-8等
------------------------
#### Document的主要方法
------------------------
document.open() // 用于新建并打开一个文档
document.close() // 不安比open方法所新建的文档
document.write() // 用于向当前文档写入内容
document.writeIn() // 用于向当前文档写入内容,尾部添加换行符
document.createEvent(type) // 创建事件对象
document.addEventListener() // 注册事件监听
document.removeEventListener() // 注销事件
document.dispatchEvent(event) // 触发事件
document.createElement(tagName) // 创建元素节点
document.createTextNode(text) // 创建文本节点
document.createAttribute(name) // 创建属性节点
document.createDocumentFragment() // 生成一个DocumentFragment对象
document.getElementById(id) // 根据id值获取指定ID的元素节点返回
document.querySelector(selectors) // 根据选择器参数获取指定的所有标签中的第一个返回
document.querySelectorAll(selectors) // 根据选择器参数获取指定的所有标签返回
document.getElementsByTagName(tagName) // 根据标签名称获取指定的所有标签返回
document.getElementsByClassName(className) // 根据class名称获取指定的所有标签返回
document.getElementsByName(name) // 根据name参数获取拥有指定name属性的元素节点返回
document.elementFromPoint(x,y) // 返回位于页面指定位置最上层的Element子节点