DOM(文档对象模型)是针对HTML和XML文档的一个API。DOM描绘了一个层次化的节点树,允许开发人员添加、删除和修改页面的某一部分。
一、Node类型
DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的。JS中的所有节点类型都继承自Node类型,共享着相同的基本属性和方法。每个节点都有一个nodeType属性表明节点类型,节点类型由下面12个数值常量表示,任何节点必属于其中之一。
- Node.ELEMENT_NODE(1);
- Node.ATTRIBUTE_NODE(2);
- Node.TEXT_NODE(3);
- Node.CDATA_SECTION_NODE(4);
- Node.ENTITY_REFERENCE_NODE(5);
- Node.ENTITY_NODE(6);
- Node.PROCESSING_INSTRUCTION_NODE(7);
- Node.COMMIT_NODE(8);
- Node.DOCUMENT_NODE(9);
- Node.DOCUMENT_TYPE_NODE(10);
- Node.DOCUMENT_FRAGMENT_NODE(11);
- Node.NOTATION_NODE(12);
通过比较someNode.nodeType与上述某个数值常量是否相等可以确定节点类型。此外,节点除了nodeType属性之外还保存有nodeName和nodeValue属性,这两个属性的值取决于节点类型。举个例子,元素节点的nodeName中保存着元素的标签名而nodeValue的值则为null。
1-节点关系
每个加点都有一个childNodes属性,其中保存着一个类数组的NodeList对象用于保存一组有序的子节点。NodeList对象的独特之处在于,它实际上是基于DOM结构动态查询的结果,因此DOM结构的变化能够自动反应到NodeList对象中。访问NodeList中的节点时可以通过方括号向数组一样访问,也可以通过item()方法访问,如以下示例:
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
每个节点都有一个parentNode属性,该属性指向该节点在文档树中的父节点。包含在childNodes列表中的所有节点具有相同的父节点,因此它们的parentNode属性指向同一个节点。此外,包含在ChildNodes列表中的每个节点相互之间都是同胞节点。通过使用列表中每个节点的previousSibling和nextSibling属性,可以访问同一列表中的任何其他节点。列表的第一个节点的previousSibling属性值为null,最后一个节点的nextSibling属性值为null。父节点有firstChild和lastChild属性分别指向其childNodes列表中的第一个和最后一个节点。最后,所有节点都有一个ownerDocument属性,该属性指向表示整个文档的文档节点。
2-操作节点
节点关系指针都是只读的,因此DOM提供了一些操作节点的方法。下面介绍几个常用的操作方法:
appendChild(node):用于向childNodes列表的末尾添加一个节点。添加节点后,childNodes的新增节点、父节点以及最后一个子节点的关系指针会得到相应更新。更新之后,appendChild()返回新增的节点。如果传入appendChild()的节点已经是文档的一部分了,那么结果就是将该节点从原来的位置转移到新的位置。任何DOM节点不能同时出现在文档中的多个位置上。
var returnedNode = someNode.appendChild(newNode);
console.log(returnedNode == newNode); // true
console.log(someNode.lastChild == newNode); // true
returnedNode = someNode.appendChild(someNode.firstChild); // 插入已存在的节点
console.log(returnedNode == someNode.firstChild); // false
console.log(returnedNode == someNode.lastChild); // true
insertBefore(node1, node2):该方法接受两个参数,要插入的节点与参考节点。插入节点会变成参考节点的前一个同胞节点(previousSibling)同时被返回。如果参考节点为null,那么将与appendChild()执行同样的操作。
// 插入到倒数第二个位置
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
// 插入到最后一个位置
returnedNode = someNode.insertBrfoer(newNode, null);
replaceChild(node1, node2):该方法接受两个参数,要插入的节点和被替换的节点。替换节点将从文档树中被移除,同时插入节点占据其位置。从技术上讲,被替换节点仍然存在文档中,但它在文档树中已经没了自己的位置。
removeChild(node):用于将某个节点从文档树种移除。
cloneChild(node):克隆某个节点。该方法可以接受一个布尔值参数,表示是否执行深复制。执行深复制时,除了克隆当前节点之外还会将其整个子节点树也克隆。若是浅复制,则仅复制当前节点。复制后返回的节点副本属于文档所有,但是没有为其制定父节点,需要通过appendChild()等方法将其添加到文档树中。cloneNode()不会复制DOM节点的JS属性,例如事件处理程序等。
normalize():处理文档树种的文本节点。由于解析器的实现或者DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找符合上述两种情况的文本节点,找到空文本节点则删除它,找到连续的文本节点则合并为一个文本节点。
二、Document类型
JS通过Document类型表示文档。document对象是HTMLDocument(继承自Document类型)的一个实例。Document节点具有以下特征:
- nodeType: 9
- nodeName: “#document”
- nodeValue: null
- parentNode: null
- ownerDocument: null
1-文档的子节点
Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment。document对象有内置的快速访问某些特殊元素的属性,如下所示:
- document.documentElement:该属性始终指向页面中的< html>元素
- document.body:指向< body>元素
- document.doctype:取得对< !DOCTYPE>的引用
2-文档信息
document对象提供了一些属性用于获取所表现的网页的一些信息,主要有下列几个属性:
- document.title:< title>元素中的文本,修改该属性可惜修改网页标题
- document.URL:网页完整的URL,即浏览器地址栏中显示的URL
- document.domain:网页的域名
- document.referrer:链接到当前页面的那个页面的URL
3-查找元素
document.getElementById():该方法接受一个参数,即要获取元素的ID。找到相应的元素则返回该元素,否则返回null。如果存在多个相同ID的元素,则返回文档中第一次出现的元素
document.getElementsByTagName():该方法接受一个参数,即要获取元素的标签名。返回一个HTMLCollection对象,包含所有符合查找标签名的元素,可以通过方括号或item()方法访问每一个项。HTMLCollection对象还有一个namedItem()方法可以通过元素的name特性取得集合中的项,而且,对命名的项也可以使用方括号语法来访问。给该方法传入参数”*”可以获取文档中所有元素。下面给出代码示例
<img src="img-1.jpg" name="img-1">
<img src="img-2.jpg">
<script>
var images = document.getElementsByTagName("img"); // 获取所有img元素
var img1 = images.namedItem("img-1"); // 获取命名为img-1的图片元素
var img1 = images["img-1"]; // 另一种等价的访问方式
var all = document.getElementsByTagName("*"); // 获取文档中所有元素
</script>
document.getElementsByName():该方法接受一个参数,查找名称。与getElementsByTagName()方法类似,该方法也返回一个HTMLCollection对象,返回所有带有给定name特性的元素
4-特殊集合
除了属性和方法之外,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象,为访问文档常用的部分提供快捷方式。
- document.anchors:文档中所有带name特性的< a>元素
- document.forms:文档中所有< form>元素
- document.images:文档中所有< img>元素
- document.links:文档中所有带href特性的< a>元素
三、Element类型
Element类型用于表现XML或HTML元素,提供了对元素标签名、子节点及特性的访问。Element节点具有以下特征:
- nodeType: 值为1
- nodeName: 元素标签名
- nodeValue: null
- parentNode: 可能是Document或Element
- 子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference
要想访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性。在HTML中,标签名始终以大写表示,而在XML则会与源代码一致,因此,可能会出现如下代码所示的问题:
var div = document.getElementById("myDiv");
console.log(div.tagName); // 输出"DIV"
// 如果需要执行某些判断,使用下面的写法更好,适用于多种文档
if (div.tagName.toLowerCase() == "div") {
// do something
}
1-HTML元素
所有HTML元素都由HTMLElement类型表示,HTMLElement类型继承自Element类型并添加了一些属性,添加的属性分别对应每个HTML元素中都存在的下列标准特性:
- id:元素在文档中的唯一标识符
- title:有关元素的附加说明信息
- lang:元素内容的语言代码,很少使用
- dir:语言的方向,值为”ltr”(从左到右)或”rtl”(从右到左)
- className:元素的class特性,即CSS类名
通过给以上属性赋值可以修改相应元素特性的值,如以下代码示例:
// 假设存在<div id="myDiv" class="bd" title="a div" lang="en" dir="ltr"></div>
var div = document.getElementById("myDiv");
console.log(div.id); // 输出"myDiv"
console.log(div.title); // 输出"a div"
div.className = "ft"; // 类名变为"ft"
2-获取和操作特性
getAttribute():该函数接受一个参数,用于获取某个特性的值。传递的参数要与实际的也姓名相同,因此当获取类名的时候要传入”class”而不是”className”,特性不区分大小写。下面给出代码示例:
var div = document.getElementById("myDiv");
console.log(div.getAttribute("id")); // 输出"myDiv"
setAttribute():该函数接受两个参数,要设置的特性名和值。通过setAttribute()方法既可以操作HTML特性也可以操作自定义特性。请看下面代码示例
div.setAttribute("class", "ft");
div.setAttribute("mycolor", "red");
removeAttribute():该方法接受一个参数,用于删除特性
3-attributes属性
Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap对象,元素的每一个特性都有一个Attr节点表示,每个节点都保存在NamedNodeMap对象中,该对象拥有以下方法。attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,nodeValue就是特性的值。
- getNamedItem(name):返回nodeName属性等于name的节点
- removeNamedItem(name):删除nodeName属性等于name的节点
- setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引
- item(pos):返回位于数字pos位置处的节点
关于attributes如何使用请看下面代码示例:
// 获取某个div元素的id的值的两种方式
var id = div.attributes.getNamedItem("id").nodeValue;
id = div.attributes["id"].nodeValue;
// 设置id的值
div.attributes["id"].nodeValue = "xxx";
4-创建元素
使用document.createElement()方法可以创建新元素。该方法接受一个参数,即要创建的元素的标签名。创建元素之后可通过属性设置相关的特性,通过appendChild()等方法添加到文档树中。
5-元素的子节点
元素可以有任意数目的子节点和后代节点。元素的childNodes属性中包含了它的所有子节点。如果想要通过特定的标签名获取子节点或后代节点,可以通过元素调用getElementsByTagName()方法,这种调用方式除了搜索起点是当前元素之外,其他方面都跟通过document调用这个方法相同。请看以下代码示例:
// 获取div元素下的img元素
var img = div.getElementsByTagName("img");
// 获取整个文档中的img元素
var allImg = document.getElementsByTagName("img");
四、Text类型
文本节点由Text类型表示,可以通过noveValue或data属性访问Text节点中包含的文本。文本节点作为元素节点的子节点,只要元素开始与结束标签之间存在内容,就会创建一个文本节点。在JS中,也可以直接使用document.createTextNode(text)创建文本节点。在JS中创建文本节点可能会使得一个父节点下面存在连续的多个文本节点,可以直接在父元素上调用normalize()方法将多个文本节点合并称为一个。使用下列方法可以操作节点中的文本。
- appendData(text):将text添加到节点的末尾
- deleteData(offset, count):从offset指定的位置开始删除count个字符
- insertData(offset, text):从offset指定的位置开始初入text
- replaceData(offset, count, text):用text替换从offset指定位置开始到offset+count为止处的文本
- splitText(offset):从offset指定的位置将当前文本节点分成两个文本节点
- substringData(offset, count):提取从offset位置开始到offset+count为止处的字符串
五、Comment类型
文档中的注释在DOM中由Comment类型表示,它与Text类型继承自相同的基类,拥有除splitText()之外的所有字符串操作方法。与Text类型相似,可通过nodeValue或data属性来获取注释内容。浏览器不会识别位于< html>标签后面的注释,如果要访问注释节点,一定要保证它是< html>元素的后代。
六、CDATASection类型
该类型只针对基于XML的文档,表示CDATA区域。
七、DocumentType类型
该类型包含与文档的doctype相关的所有信息
八、DocumentFragment类型
在所有的节点类型中只有DocumentFragment在文档中没有对应的标记。DOM规定文档片段是一种”轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。不能把文档片段直接添加到文档中,但可以将它作为一个仓库来使用,在里面保存将来可能添加到文档中的节点。创建文档片段的方法如下:
var fragment = document.createDocumentFragment();
如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点,浏览器中也不再显示这些节点。添加到文档片段中的新节点同样也不属于文档树。可以通过appendChild()或insertBefore()将文档片段中的内容添加到文档中。在将文档片段作为参数传递给这两个方法时,实际上只会将文档片段的所有子节点添加到相应的位置上,文档片段本身永远不会成为文档树的一部分。以下面代码为例,如果逐个地添加列表项,将导致浏览器反复渲染新信息,使用一个文档片段来保存创建的列表项,最后一次性地将它们添加到文档中,就可以完美地避开反复渲染的问题。
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i = 0; i < 10; ++i) {
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li); // 添加新元素到文档片段中
}
ul.appendChild(fragment); // 一次性将文档片段中的内容添加到文档
九、Attr类型
该类型用于表示元素的特性,由于有getAttribute()等方法,因此很少使用这个类型来操作元素特性。