DOM 对象及其属性方法

1. 节点类型

每个节点有一个类型(Node Type)属性,它通常有数值常量和字符常量两种表示方式,IE 只支持数值常量方式。

节点类型有 12 种,下图中只展示了其中 7 种。

1.1 元素节点 Element

元素节点是组成文档树的重要组成部分,数值常量为 1,字符常量为 ELEMENT_NODE。

该节点通常拥有子元素、文本节点,或二者的结合,是唯一具有属性的节点类型。

1.2 属性节点 Attr

属性节点数值常量为 2,字符常量为 ATTRIBUTE_NODE。

由于属性是附属于元素的,因此不能视为元素节点的子节点。所以属性并不被认为是 DOM 的一部分。

例如下图中的 langcharsetid

1.3 文本节点 Text

文本节点数值常量为 3,字符常量为 TEXT_NODE。

文本节点是包含文本内容的节点,它可以只包含空白或文本内容。

例如下图中的 DOM 操作您目前已登陆账号

另外,标签后面的空白也被认为是文本节点。例如:

1.4 注释节点 Comment

注释节点数值常量为 8,字符常量为 COMMENT_NODE,是包含注释内容的节点。例如:

1.5 注释节点 Comment

注释节点数值常量为 9,字符常量为 DOCUMENT_NODE,是文档树的根节点,是其它所有节点的父节点。

注:它并不是 HTML 的根元素。由于处理指令、注释等可以出现在根元素之外,因此构造 DOM 树时根元素并不适合做文档节点。根元素是作为文档节点的子节点出现的。

如图,我们认为代码之上是文档节点。文档节点包含一个「文档类型节点」<!DOCTYPE html>和「元素节点」 <html>

1.6 文档类型节点 DocumentType

文档类型节点数值常量为 10,字符常量为 DOCUMENT_TYPE_NODE。

每一个文档都有一个 DocType 属性,或者为 Null 或者为 DocType 对象。

1.7 文档片段节点 DocumentFragment

文档类型节点数值常量为 11,字符常量为 DOCUMENT_TYPE_NODE。

文档片段代表文档中的一部分或一段,是轻量级的 document 对象,但不属于 DOM 树。但它有一种特殊的行为,比如当请求把 DocumentFragment 节点插入文档时,会将其子孙节点插入。同时有利于实现文档的复制、剪切等操作。

例如下图的 frag 保存了文档片段节点:

2. Document 对象属性及方法

2.1 属性

2.1.1 documentElement

返回文档最外层元素的引用,即根元素,它总是指 <html> 元素。

1
2
3
4
5
document.documentElement;
// <html>...</html>

Object.prototype.toString.call(document.documentElement)
// [object HTMLHtmlElement]

2.2 方法

2.2.1 getElementById()

返回带有指定 ID 的元素,如果不存在,则返回 null。

1
2
3
4
5
6
var tip = document.getElementById('tip'); //

Object.prototype.toString.call(tip)
//[object HTMLDivElement];

document.getElementById('tips'); // null

2.2.2 getElementsByTagName()

返回包含带有指定标签名称的所有元素的节点数组,如果不存在,返回空的类数组。

1
2
3
4
5
6
7
8
var eleList = document.getElementsByTagName('div');
// [div#tip, div, tip: div#tip]

Object.prototype.toString.call(eleList)
// [object HTMLCollection]

document.getElementsByTagName('p');
// []

注:该方法返回的是类数组,而非数组。可以用下面的方法将其转化为数组:

1
var array = Array.prototype.slice.call(fakeArray);

2.2.3 querySelector(cssSelector)

返回与给定 css 选择器匹配的第一个元素的引用(节点),如果不存在,则返回 null。

1
2
3
4
5
6
7
8
var firstLi = document.querySelector('#list li');
// <li>1</li>

Object.prototype.toString.call(firstLi)
//[object HTMLLIElement]

document.querySelector('#list ul');
// null

2.2.4 querySelectorAll(cssSelector)

返回与给定 css 选择器匹配的一组元素的引用(节点列表)。

注:该方法返回的是类数组 [HTMLCollection],而非数组。转换方式同上。

1
2
3
4
5
6
7
8
var list = document.querySelectorAll('#list li');
// [li, li, li, li, li]

Object.prototype.toString.call(list);
// [object NodeList]

document.querySelectorAll('#list ul');
// []

注:querySelectorAll 返回的虽然是 NodeList ,但是实际上是元素集合,并且是静态的(其他接口返回的 HTMLCollection 和 NodeList 都是 live 的)

2.2.5 createElement(elementName)

创建一个新的元素节点。

创建后通常配合 appendChild()insertChild() 方法一起使用。例如:

1
2
3
4
5
<ul id="list">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
</ul>
1
2
3
var newEle = document.createElement("li");
newEle.innerHTML = "Juice"
document.getElementById('list').appendChild(newEle);

上面代码实现了创建一个元素后加入到列表中,结果如下:

1
2
3
4
5
6
<ul id="list">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
<li>Juice</li>
</ul>

2.2.6 createTextNode(text)

创建新的文本节点。createTextNode 通常配合 createElement 一起使用,实现为某个元素节点添加文本节点。

1
2
3
let newNode = document.createElement('li');
let text = document.createTextNode('Juice');
newNode.appendChild(text);

上述代码会创建一个 <li>Juice</li> 的节点。

注:innerHTMLcreateTextNode 的区别

下面两种写法的结果是一样的:

1
2
3
4
5
ele.innerHTML = "Juice"

// 等价于
var textNode = document.createTextNode("Juice");
ele.appendChild(textNode);

二者的区别在于 innerHTML 可以添加 HTML 标签,会被当做 HTML 语法进行解析。对比以下两种情况:

1
2
document.createTextNode('<br>Juice</br>');
newNode.innerHTML = '<br>Juice</br>';

得到的结果是:

createTextNode 方法:

innerHTML 方法:

2.2.7 createAttribute(name)

创建新的属性节点。

通常需要配合 value 属性、setAttributeNode 方法和 setAttribute(name, value) 方法一起使用,实现为某个元素节点添加属性节点。

1
<li id="item1">Coffee</li>
1
2
3
4
let att = document.createAttribute("class");
att.value = "red-color";

document.getElementById("item1").setAttributeNode(att);

结果为:

1
<li id="item1" class="red-color">Coffee</li>

一些需要注意的点

HTMLCollection 与 NodeList

HTMLCollection 是元素集合而 NodeList 是节点集合(既可以包含元素,也可以包含文本节点)。

3. Element 对象属性及方法

3.1 属性

3.1.1 tagName

获取元素的标签名称,返回值为 String 类型。

1
2
document.getElementById("tip").tagName; 
// DIV

注:在 HTML 中,tagName 属性的返回值始终是大写的。

3.2 方法

3.2.1 getAttribute(attributeName)

获取属性的值,类型为 String。如果该属性不存在,则返回 null。

1
2
3
4
5
document.getElementById("tip").getAttribute('id');
// tip

document.getElementById("tip").getAttribute('a');
// null

另外,有 getAttributeNode(attributename) 方法,用于获取属性节点。

3.2.2 setAttribute(attributeName, attributeValue)

用指定的值设置属性

1
document.getElementById("tip").setAttribute('align', 'center');

设置后 DOM 中增加了 align 属性:

1
<div id="tip" align="center">您目前已登陆账号</div>

另外,还有一个类似的方法 setAttributeNode(),功能与 setAttribute() 相同,但传入的参数不相同。

1
2
3
4
let att = document.createAttribute("align");
att.value = "center";

document.getElementById("tip").setAttributeNode(att);

3.2.3 removeAttribute(attributeName)

从元素中删除指定的属性及其值

1
document.getElementById("tip").removeAttribute('align', 'center');

执行后 DOM 中删除了 align 属性:

1
<div id="tip">您目前已登陆账号</div>

4. Node 对象属性及方法

4.1 属性

4.1.1 firstChild

获取指定节点的首个子节点。

1
2
3
4
5
<ul id="list">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3"></li>
</ul>
1
2
document.getElementById('list').firstChild;
// <li id="item1"></li>

注:在现代浏览器下,比如 Chrome、 FF 和 IE11 等会把 <ul><li> 两个标签之间的空白节点也解析出来,所以获取到的是 #text,即空白字符会被作为文本节点,如下:

所以如果你的结构是上面那样,用 document.getElementById("list").firstChild; 取到的只能是一个空的文本节点,而不是期望的 <li> 元素节点。

如果你想实现上面的效果,正确的写法应该是:

1
<ul id="list"><li id="item1"></li><li id="item2"></li><li id="item3"></li></ul>

或者可以用 children[0] 方法代替:

1
document.getElementById('list').children[0];

4.1.2 lastChild

获取指定节点的最后一个子节点。

1
2
3
4
5
<ul id="list">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3"></li>
</ul>
1
2
document.getElementById('list').lastChild;
// <li id="item3"></li>

lastChild 同样存在返回空的文本节点的问题,问题原因与解决方法同 firstChild

4.1.3 previousSibling

4.1.4 nextSibling

4.1.5 ownerDocument

4.1.6 parentNode

节点(元素)的父节点

4.1.7 childNodes

获得元素的子节点集合,返回类型为一个数组。

由 childNodes 属性返回的数组中包含着所有类型的节点,所有的文本节点也包含在其中。

注:属性不是节点,因此不会出现在数组里。

1
2
3
4
5
<ul id="list">
<li id="item1" align="center">aaa</li>
<li id="item2"></li>
<li id="item3"></li>
</ul>
1
2
3
4
5
6
7
let childnodes = document.getElementById('list').childNodes;
console.log(childnodes)
for (var i = 0; i < childnodes.length; i++) {
console.log("nodeType:" + childnodes[i].nodeType);
console.log("nodeName:" + childnodes[i].nodeName);
console.log("nodeValue:'" + childnodes[i].nodeValue + "'");
}

输出结果:

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
7
nodeType:3
nodeName:#text
nodeValue:'
'
nodeType:1
nodeName:LI
nodeValue:'null'
nodeType:3
nodeName:#text
nodeValue:'
'
nodeType:1
nodeName:LI
nodeValue:'null'
nodeType:3
nodeName:#text
nodeValue:'
'
nodeType:1
nodeName:LI
nodeValue:'null'
nodeType:3
nodeName:#text
nodeValue:'
'

与 childNodes 类似的有一个 children 属性,它返回指定元素的子元素集合。但它只返回 HTML 节点,甚至不返回文本节点。虽然不是标准的 DOM 属性,但它和 innerHTML 方法一样,得到了几乎所有浏览器的支持。

因此如果想获取指定元素的第一个元素节点,可以使用 children[0] 来替代上面的 firstChild。

注:childNodes 是标准属性,而 children 不是标准属性,

4.1.8 nodeName

获得节点名称。

  • 如果节点是元素节点,返回节点的名字。此时,相当于 tagName 属性
  • 如果是属性节点,nodeName 将返回这个属性的名字
  • 如果是文本节点,nodeName 将返回一个 #text 的字符串

下面是个栗子:

1
<li id="item1" align="center">aaa</li>
1
2
3
4
5
6
7
8
9
10
11
// 元素节点
document.getElementById('item1').nodeName;
// LI

// 文本节点
document.getElementById('item1').childNodes[0].nodeName;
// #text

// 属性节点
document.getElementById('item1').getAttributeNode('align').nodeName;
// align

注:nodeName 返回的元素节点名称为大写字母。

4.1.9 nodeType

获取节点类型的数值常量,各节点数值常量参见第一节内容。

下面是个栗子:

1
<li id="item1" align="center">aaa</li>
1
2
3
document.getElementById('item1').nodeType; // 1
document.getElementById('item1').childNodes[0].nodeType; // 3
document.getElementById('item1').getAttributeNode("id").nodeType; // 2

4.1.10 nodeValue

获取节点的值

  • document 文档:返回 null
  • 元素节点:返回 null
  • 文本节点:返回其节点的内容
  • 注释节点:返回其节点的内容
1
2
3
4
document.nodeValue; // null
document.getElementById('item1').nodeValue; // null
document.getElementById('item1').childNodes[0].nodeValue; // aaa
document.getElementById('item1').getAttributeNode("id").nodeValue; // item1

4.2 方法

4.2.1 appendChild(newNode)

将一个新 node 对象添加到自节点列表的末尾,返回追加的节点。

假如当前页面有以下 DOM 结构:

1
2
3
4
5
<ul id="list">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3"></li>
</ul>

用法一:添加文本/元素节点

1
2
3
4
5
var text = document.createTextNode("我是文本");
document.getElementById("item3").appendChild(text);

var ele = document.createElement('li');
document.getElementById("list").appendChild(ele);

执行上述代码后:

1
2
3
4
5
6
7
<ul id="list">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3">我是文本</li>
<!-- 下面新添加的 DOM 节点 -->
<li></li>
</ul>

用法二:转移某个列表项到另外一个列表项

1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li id="item1">Coffee</li>
<li id="item2">Tea</li>
<li id="item3">Water</li>
</ul>
1
2
var node = document.getElementById("list1").lastChild;
document.getElementById("list2").appendChild(node);
1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
</ul>

<ul id="list2">
<li id="item1">Coffee</li>
<li id="item2">Tea</li>
<li id="item3">Water</li>
<li>3</li>
</ul>

注:
1、如果被插入的节点已经存在,则从当前父节点中删除,添加到新的父节点。
2、空白字符会被作为文本节点,如下:

所以如果你的结构是上面那样,用 document.getElementById("list1").lastChild; 取到的只能是一个空的文本节点,而不是期望的 <li> 元素节点。

如果你想实现上面的效果,正确的写法应该是:

1
2
<ul id="list1"><li>1</li><li>2</li><li>3</li></ul>
<ul id="list2"><li id="item1">Coffee</li><li id="item2">Tea</li><li id="item3">Water</li></ul>

当然,这种写法很变态,所以如果你想取元素,可以用 lastElementChild 代替 lastChild

4.2.2 removeChild(childNode)

删除子节点

1
2
3
4
5
<ul id="list">
<li id="item1">Coffee</li>
<li id="item2">Tea</li>
<li id="item3">Water</li>
</ul>
1
2
list = document.getElementById("list");
list.removeChild(list.childNodes[0]);

执行上面的代码后,DOM 结构发生了改变,我们如愿删除了第一个子元素:

1
2
3
4
<ul id="list">
<li id="item2">Tea</li>
<li id="item3">Water</li>
</ul>

事实上,childNodes[0]firstChild 面临着相同的问题,都会取到文本节点。我们可以用 list.children[0] 来代替。需要注意的是,firstChild 和 childNodes 才是标准的,而 children 不是标准的。

4.2.3 replaceChild()

1
2
3
4
5
<ul id="list">
<li id="item1">Coffee</li>
<li id="item2">Tea</li>
<li id="item3">Water</li>
</ul>
1
2
3
let newNode = document.createTextNode('Juice');
let item = document.getElementById("list").childNodes[0];
item.replaceChild(newNode, item.childNodes[0]);
1
2
3
4
5
<ul id="list">
<li id="item1">Juice</li>
<li id="item2">Tea</li>
<li id="item3">Water</li>
</ul>

上面是替换文本节点,另外还可以替换元素节点。

注:如果替换的节点是已存在的节点,则原节点被删除,替换新的节点。如下:

1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
</ul>
1
2
3
4
let newNode = document.getElementById("list1").childNodes[0];
let list = document.getElementById("list2");
let item = document.getElementById("list2").childNodes[0];
list.replaceChild(newNode, item);
1
2
3
4
5
6
7
8
9
10
<ul id="list1">
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>1</li>
<li>Tea</li>
<li>Water</li>
</ul>

4.2.4 cloneNode(deep)

创建节点的拷贝,并返回该副本。该方法会克隆所有属性以及它们的值。

参数: deep

  • true,克隆节点及其属性,以及后代
  • false,默认值,只克隆节点及其后代,不复制属性。
1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
</ul>
1
2
3
let node = document.getElementById("list1").childNodes[0];
let cln = node.cloneNode(true);
document.getElementById("list2").appendChild(cln);

结果:

1
2
3
4
5
6
7
8
9
10
11
12
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
<li>1</li>
</ul>

如果将 true 改为 false,那么将不会把它的文本子节点克隆过来:

1
2
3
4
5
6
7
8
9
10
11
12
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
<li></li>
</ul>

4.2.5 hasChildNodes()

检查元素是否有子节点。如果指定节点拥有子节点返回 true,否则返回 false。

1
2
3
4
<ul id="list1">
<li id="item1">1</li>
<li id="item2"></li>
</ul>
1
2
3
document.getElementById('list1').hasChildNodes(); // true
document.getElementById('item1').hasChildNodes(); // true
document.getElementById('item2').hasChildNodes(); // false

4.2.6 insertBefore(newNode, referenceNode)

在指定的子节点前面插入新的子节点。

  • newNode:被插入的节点
  • referenceNode:需插入哪个节点之前
1
2
3
4
5
<ul id="list">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
</ul>
1
2
3
4
5
6
7
// 创建新节点
let newNode = document.createElement('li');
let newTextNode = document.createTextNode('Juice');
newNode.appendChild(newTextNode);

let referenceNode = document.getElementById('list').lastChild;
list2.insertBefore(newNode, referenceNode)

添加子节点后:

1
2
3
4
5
6
<ul id="list">
<li>Coffee</li>
<li>Tea</li>
<li>Juice</li>
<li>Water</li>
</ul>

如果插入的新节点是原来已存在的节点,那么原节点会被删除,增加到新的位置上。

1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
</ul>
1
2
3
4
let newNode = document.getElementById('list1').lastChild;

let referenceNode = document.getElementById('list2').lastChild;
list2.insertBefore(newNode, referenceNode)

结果如下:

1
2
3
4
5
6
7
8
9
10
11
<ul id="list1">
<li>1</li>
<li>2</li>
</ul>

<ul id="list2">
<li>Coffee</li>
<li>Tea</li>
<li>3</li>
<li>Water</li>
</ul>