JavaScript——DOM

Posted by Csming on 2018-09-24

DOM是什么

DOM是针对HTML和XML文档的一个API。

DOM描绘了一个层次化的节点树。

DOM允许开发人员:添加、移除、修改页面的某一部分。

现在DOM已经成为表现和操作页面标记的真正跨平台、语言中立的方式了。

DOM1级

DOM1提供了基本的文档结构及查询的接口。

节点层次

DOM可以将任何的HTML或者XML文档描绘成一个由多层节点构成的结构。

节点分为几种不同的类型。每种类型分别表示文档中不同的信息及标记。每个节点都拥有各自的特点、数据和方法,以及和其他节点存在的某种关系。

每一段标记都可以通过树中的一个节点来表示: HTML元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点表示,注释通过注释节点表示。

总共有12中节点类型,这些类型都继承自一个基类: Node类型

Node类型

DOM1级定义了一个Node接口,这个接口将由DOM中的所有节点类型实现。

Node接口,在JavaScript中是作为Node类型实现的。(除了IE之外,其他浏览器都可以访问到这个类型。)

所有节点类型都共享相同的基本属性和方法:

nodeType属性 用于表明节点的类型。节点类型在Node中定义类12个常量

(1)Node.ELEMENT_NODE(1)
(2)Node.ATTRIBUTE_NODE(2)
(3)Node.TEXT_NODE(3)
(4)Node.CDATA_SECTION_NODE(4)
(5)Node.ENTITY_REFERENCE_NODE(5)
(6)Node.ENTITY_NODE(6)
(7)Node.PROCESSING_INSTRUCTION_NODE(7)
(8)Node.COMMENT_NODE(8)
(9)Node.DOCUMENT_NODE(9)
(10)Node.DOCUMENT_TYPE_NODE(10)
(11)Node.DOCUMENT_FRAGMENT_NODE(11)
(12)Node.NOTATION_NODE(12)

可以通过比较上面的常量,以确定节点的类型:

1
2
3
4
5
6
7
8
9
10
if (someNode.nodeType == Node.ELEMENT_NODE) {
alert("Node is an element");
}

// 由于在IE中无法访问Node,故上述代码无效。
// 可用以下代码代替:

if (someNode.nodeType == 1) {
alert("Node is an element");
}

nodeName和nodeValue属性

通过nodeName和nodeValue可以了解节点的具体信息。

节点关系

文档中的所有节点之间都存在关系。

以HTML中为例:<body>元素可以看成是<html>元素的子元素,而<html>就是<body>的父元素。
<head>元素和<body>元素是同胞元素,因为都是<html>的直接子元素。

每个节点都有一个childNodes属性,其中保存着一个NodeList对象。 NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置访问这些节点。

NodeList对象是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。

1
2
3
var firstChild = someNode.childNodes[0];
var secondeChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;

length属性表示的是,访问NodeList的那一刻,其中包含的节点数。

可以使用Array.prototype.slice()方法将NodeList转换为数组。

1
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);

但是,如果想要在IE中将NodeList转换为数组,必须手动枚举所有成员:

1
2
3
4
5
6
7
8
9
10
11
function convertToArray(nodes) {
var array = null;
try {
array = Array.prototype.slice.call(nodes, 0);
} catch(ex) {
array = new Array();
for(var i = 0, len = nodes.lengthm i < len; i++) {
array.push(nodes[i]);
}
}
}

每个节点有一个parentNode属性,用于指向父节点。而在同一个父元素下的每个子节点中有previousSibling和nextSibling属性,用于访问同一列表中的其他节点。

另外,父节点有firstChild和lastChild属性来指向第一个和最后一个子节点。

最后,所有节点都有一个属性:ownerDocument,用于访问整个文档的文档节点。

操作节点

由于关系指针都是只读的,故DOM提供了一些操作节点的方法;

appendChild(): 用于向childNodes列表的末尾添加一个节点。添加节点后,childNodes的新增节点、父节点以之前的最后一个子节点的关系指针都会得到更新。

更新后,appendChild()返回新增的节点:

1
2
3
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode);
alert(someNode.lastChild == newNode);

如果传入到appendChild()的节点已经是文档的一部分了。那么这个节点将会从原来的位置移到新的位置。因为任何DOM节点都不能同时出现在文档中的多个位置上。

1
2
3
var returnedNode = someNode.appendChild(someNode.firstNode);
alert(returnedNode == someNode.firstChild); // false
alert(returnedNode == someNode.lastChild); // true

insertBefore()方法,能够将把节点放在childNoes列表中的某个特定的位置上。需要传入两个参数:要插入的节点和作为参照的节点: 插入节点后,被插入的节点会变成参照节点的前一个通报节点,同事被方法返回。

1
2
3
4
5
6
7
8
9
returnedNode = someNode.insertBefore(newNode, null); // 插入后成为最后一个子节点
alert(newNode == someNode.lastChild); // true

var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); // 插入后城后第一个子节点
alert(returnedNode == newNode); // true
alert(newNode == someNode.firstChild); // t

returnedNode = someNode.insertBefore(newNode, someNode.lastChild); // 插入到最后一个子节点前
alert(newNode == someNode.childNodes[someNode.childNodes.lenght - 2]);// true

replaceChild()能够移除节点,接收两个参数:要插入的节点和要替换的节点: 要替换的节点,将会由这个方法返回,并且从文档树中被移除。同时,由要插入的节点占据其位置。

1
2
3
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild); // 替换第一个子节点

returnedNode = someNode.replaceChild(newNode, someNode.lastChild); // 替换最后一个子节点

removeChild()方法可以移除一个节点, 接收一个参数:要移除的节点

1
2
3
var formerFirstChild = someNode.removeChild(someNode.firstChild); // 移除第一个子节点

var formerLastChild = someNode.removeChild(someNode.lastChild); // 移除最后一个子节点

其他方法

cloneNode()方法用于创建调用这个方法的节点的一个完全相同的副本;其接收一个参数: 是否执行深拷贝 在参数为true的时候,会复制节点及其整个子节点树。在参数为false的时候,只赋值节点本身。

复制后返回的节点副本属于文档所有,但没有为它指定父节点。如果不使用节点操作方法将其添加到文档中,他会成为一个”孤儿“

1
2
3
4
5
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length);

var shallowList = myList.cloneNode(false);
alert(shallow.childnodes.length);

normalize()方法,可以处理文档树中的文本节点。

由于文本节点可能不包含文本,或者接连出现两个文本节点的情况。这个方法能够在调用该方法的节点的后代节点中查找这两种情况的节点,并删除它们。

Document类型

js通过Document类型表示文档。

在浏览器中,document对象是HTMLDocument的一个实例。表示了整个HTML页面。document对象是window对象的一个属性。

Document节点具有下列特征:

1.nodeType: 9
2.nodeName: "#document"
3.nodeValue: null
4.parentNode: null
5.ownerDocument: null

其子节点可能是一个DocumentType、Element、ProcessingInstruction或Comment;

Document类型表示HTML页面或者其他基于XML的文档。

通过document对象,我们可以取得与页面有关的信息,并且能够操作页面的外观及其底层结构。

文档的子节点

访问子节点的方式:

1.通过documentElement访问HTML页面中的<html>元素。
2.通过childNodes列表访问文档元素。
3.通过body访问<body>元素。
4.通过doctype可以访问<!DOCTYPE>的信息

1
2
3
var html = document.documentElement;

var body = document.body;

文档信息

document对象提供了一些网页的信息;

1.title: 包含了<title>元素中的文本。可以取得当前页面的标题。
2.URL: 包含了页面完整的URL。
3.domain: 包含了页面的域名
4.referrer: 保存了链接到当前页面的那个页面的URL。在没有来源页的时候表现为空字符串。

上述的信息中,只有domain是可以设置的。但只能设置为URL中包含的域。

查找元素

在DOM的应用中,常需要获得某个或某组元素的引用,然后对这些元素做一些操作。

取得元素的操作可以由document对象的几个方法来实现:

1.getElementById(): 该方法接收元素的ID。若找到对应元素则返回该元素,若找不到则返回null。 若页面中存在多个id相同的元素,则获取第一个出现的元素。

2.getElementsByTagName(): 该方法接收元素的标签名,然后返回包含零或多个元素的NodeList。在HTML文档中,会返回一个HTMLCollection对象。

1
2
3
4
5
var images = document.getElementByTagName("img");

images.length;
images[0].src;
images.item(0).src;

HTMLCollection还有一个namedItem()方法: 可以通过元素的name特性获得集合中的项。

1
2
3
var myImage = images.namedItem("myImage");

var myImage = images["myImage"];

3.getElementsByName(): 该方法会返回给定name特性的所有元素。

特殊集合

除了属性和方法外,document对象还有一些特殊的集合。

(1)document.anchors 包含文档所有带name特性的<a>元素
(2)document.applets 包含文档所有的<applet>元素
(3)document.forms 包含文档所有的<form>元素
(4)document.images 包含文档所有的<img>元素
(5)document.links 包含文档所有带href特性的<a>元素

DOM一致性检测

可以利用document.implementation的hasFeature()方法判断浏览器对DOM的实现。该方法接收要检测的DOM功能和版本号。

1
var hasXmlDom = document.implementation.hasFeature("XML", "1.0");

文档写入

将输入流写入到网页中:write(), writeln(), open(), close().

write()和writeln()方法: 接收要写入输出流中的文本。则write()方法将接收的文本直接写入,而writeln()方法会在字符串的末尾添加一个换行符。

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<p>The current date and time is:
<script type="text/javascript">
document.write("<strong>" + (new Date()).toString() + "</strong>")
</script>
</p>
</body>
</html>

上述代码,将会创建一个DOM元素。

write()和writeln()可以动态的包含外部资源。

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<script type="text/javascript">
document.write("<script type=\"text/javascirpt\" src=\"file.js\">"
+ "<\/script>")
</script>
</body>
</html>

Element类型

Element类型用于表现XML或HTML元素。

Element具有下列特征:

1.nodeType: 1
2.nodeName: 为标签名
3.nodeValue: null
4.parentNode: Document或Element 而其子节点可能为Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference。

可以使用nodeName或者tagName访问元素的标签名。

1
2
3
var div = document.getElementById("myDiv");
div.tagName;
div.nodeName;

HTML元素

所有的HTML元素都由HTMLElement类型表示。

每个HTML元素都存在以下特性:

1.id: 元素在文档中的位移标识符
2.title: 有关元素的附加说明信息
3.lang: 元素内容的语言代码
4.dir: 语言的方向。
5.className: 与元素的class特性对应,即为元素指定的CSS类。

操作特性

每个元素都有一或多个特性,这些特性的用途是给出相应元素或内容的附加信息。能够以三个方法操作特性:

1.getAttribute(): 获取指定特性名的特性值。

1
2
3
4
5
6
7
8
9
10
11
var div = document.getElementById("myDiv");
div.getAttribute("id");
div.getAttribite("class");
div.getAttribute("title");
div.getAttribite("lang");
div.getAttribute("dir");

// 也可以通过属性的方式获取
div.id;
div.title;
div.align;

有两种特殊特性:style和onclick。
style在通过getAttribute()方法获取时,会返回一个字符串,而通过属性的方式访问时会返回一个对象。

onclick:在元素上使用时,onclick特性包含的是js代码如果通过getAttribute()方法访问则返回字符串。而通过属性访问时,则会返回一个函数。

2.setAttribute(): 接收两个参数,一个是特性名,一个是值。若特性已经存在,则setAttribute()指定的值会替换原有的值。若不存在,则创建该属性,并设置相应的值。

1
2
3
4
5
6
7
8
9
10
div.setAttribute("id", "someOtherId");
div.setAttribute("class", "ft");
div.setAttribute("title", "Some other text");
div.setAttribute("lang", "fr");
div.setAttribute("dir", "rtl");

// 也可以通过属性的方式操作
div.id = "someOtherId";
div.title = "Some other text";
div.lang = "fr";

3.removeAttribute(): 该方法会将特性直接删除。

attributes属性

Element类型是使用attributes属性的唯一一个DOM节点类型。

attributes包含一个NamedNodeMap。元素的每一个特性都由一个Attr节点表示。NamedNodeMap包含以下方法:

1.getNamedItem(name): 返回nodeName属性等于name的节点。
2.removeNamedItem(name): 从列表中删除nodeName属性等于name的节点
3.setNamedItem(node): 向列表中添加节点,以节点的nodeName属性为索引
4.item(pos): 返回位于pos位置的节点。

1
2
3
4
5
6
7
8
9
10
var id = element.attributes.getNamedItem("id").nodeValue;

var id = element.attributes["id"].nodeValue;

element.attributes["id"].nodeValue = "someOtherId";


var oldAttr = element.attributes.removeNamedItem("id");

element.attributes.setNamedItem(newAttr);

创建元素

可以使用document.createElement()方法创建新元素。该方法接收一个参数:要创建元素的标签名。

1
var div = document.createElement("div");

然后可以将元素添加到文档中。

元素的子节点

元素的childNodes包含了它的所有子节点。这些子节点可能是元素,文本节点,注释或处理指令。

Text类型

文本节点由Text类型表示。包含的可以是纯文本内容。纯文本可以包含转义后的HTML字符,但不能包含HTML代码。

Text节点具有以下特征:

1.nodeType: 3
2.nodeName: "#text"
3.nodeValue: null
4.parentNode: Element
5.没有子节点 ,可以通过nodeValue或者data属性访问&修改Text节点中包含的文本。

Text节点具有以下方法:

1.appendData(text): 将text添加到节点末尾
2.deleteData(offset, count): 删除从offset开始的count歌字符。
3.insertData(offset, text): 在offset的位置插入text
4.replaceData(offset, count, text): 用text替换从offset指定的位置开始的count个字符。
5.spliteText(offset): 从offset指定的位置将当前文本节点分成两个文本
6.substringData(offset, count): 提取从offset指定的位置开始的count个字符的字符串。


在默认情况下,每个可以包含内容的元素最多只有一个文本节点,并且确实有内容存在。

1
2
3
4
5
6
7
8
<!-- 没有文本节点 -->
<div></div>

<!-- 有一个文本节点 -->
<div> </div>

<!-- 有一个文本节点 -->
<div>Hello World!</div>

创建文本节点

可以使用document.createTextNode()创建新的文本节点。该方法传入要插入节点中的文本。

1
var textNode = document.createTextNode("xxxxx");

在创建新文本节点的同时,也会为其设置ownerDocument属性。不过,除非把新节点添加到文档树中已存在的节点中,否则我们不会再浏览器窗口中看到新节点。

正常情况下,每个元素只有一个文本节点,但是特定情况下也会存在多个:

1
2
3
4
5
6
7
8
9
10
var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello World");
element.appendChild(textNode);

var anotherTextNode = docuemnt.createTextNode("Yippee!");
element.appendChild(anotherTextNode);

document.body.appendChild(element);

上述代码将会在element节点下创建两个文本节点。

规范化文本节点

DOM文档存在相邻的同胞文本节点会导致混乱。

故,可以使用Node类型提供的normalize()方法将所有文本节点合并成一个文本节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello World");
element.appendChild(textNode);

var anotherTextNode = docuemnt.createTextNode("Yippee!");
element.appendChild(anotherTextNode);

document.body.appendChild(element);

element.childNodes.length; // 2

element.normalize();
element.childNodes.length; // 1
element.firstChild.nodeValue; // "Hello World!Yippee!"

分割文本节点

splitText()方法能够将一个文本节点分成两个文本节点。
即,指定位置分割nodeValue)

原本的文本节点将会包含从开始到指定位置之前的内容。新文本节点将会包含剩下的文本,并被返回。

1
2
3
4
5
6
7
8
9
10
11
12
var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello World");
element.appendChild(textNode);

document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);
element.firstChild.nodeValue; // "Hello"
newNode.nodeValue; // " World!"
element.childNodes.length; // 2

Comment类型

Comment是DOM中的注释。

Comment类型包含以下特征:

1.nodeType: 8
2.nodeName: "#comment"
3.nodeValue: 注释的内容
4.parentNode: Document或Element
5.没有子节点

Comment议程于Text类型。拥有Text类型中除了splitText外的所有方法。Comment一般很少被用到。

CDATASection类型

CDATASection类型只针对XML的文档。表示CDATA区域。

CDATASection类型也继承于Text类型。拥有除了splitText外的所有Text方法。

CDATASection节点具有下列特征:

1.nodeType: 4
2.nodeName: "#cdata-section"
3.nodeValue: CDATA区域的内容
4.parentNode: Document或Element
5.没有子节点

DocumentType类型

DocumentType类型在浏览器中不太常用;

DocumentType具有文档的doctype有关的所有信息,其有以下特征:

1.nodeType: 10
2.nodeName: doctype的名称
3.nodeValue: null
4.parentNode: Document
5.没有子节点

DocumentFragment类型

DocumentFragment在文档中没有对应的标记。

DOM规定文档片段是一种轻量级的文档,可以包含和控制节点,但不想完整的文档那样占用额外的资源。

DocumentFragment节点具有以下特征:

1.nodeType: 11
2.nodeName: "#document-fragment"
3.nodeValue: null
4.parentNode: null
5.子节点可以是Element、ProcessingInstruction、Comment、Text、CDATASectin或者EntityReference

虽然不能将文档片段加入文档,但是可以将其作为一个“仓库”使用。在里面保存将要添加到文档中的节点。

文档片段继承了Node的所有方法,通常用于执行针对文档的DOM操作。 如果文档中的节点添加到文档片段中,就会从文档树中移除该节点。

可以通过appendChild()或insertBefore()方法将文档片段中的内容添加到文档中。这样做会将文档片段的所有子节点添加到相应位置。

1
2
3
4
5
6
7
8
9
10
11
12
var fragment = document.createDocumentFragment();

var ul = document.getElementById("myList");

var il = null;

for (var i = 0; i < 3; i++) {
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i + 1)));
fragment.appendChild(li);
}
ul.appendChild(fragment);

Attr类型

元素的特性在DOM中以Attr类型表示。

1.nodeType: 11
2.nodeName: 特性的名称
3.nodeValue: 特性的值
4.parentNode: null
5.不支持子节点,但是在XML中的子节点可以是Text或EntityReference。

DOM操作技术

动态脚本

页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function loadScript(url) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}

function loadScript(code) {
var script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(code);
}catch(ex) {
script.text = code;
}


document.body.appendChild(script);
}

动态样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function loadStyle(url) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}

function loadStyle(css) {
var style = document.createElement("style");
style.type = "text/css";
try {
stype.appendChild(document.createTextNode(css));
}catch(ex) {
style.stypeSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}

操作表格

太麻烦了,略。

使用NodeList

NodeList是动态的,当文档结构变化时,他们都会得到更新。因此,NodeList会保存着最新最准确的信息。