本章内容
文档对象模型(DOM,Document Object Model)是HTML 和XML 文档的编程接口。DOM 表示由多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。脱胎于网景和微软早期的动态HTML(DHTML,Dynamic HTML),DOM 现在是真正跨平台、语言无关的表示和操作网页的方式。
任何HTML 或XML 文档都可以用DOM表示为一个由节点构成的层级结构。节点分很多类型,每种类型对应着文档中不同的信息和(或)标记,也都有自己不同的特性、数据和方法,而且与其他类型有某种关系。这些关系构成了层级,让标记可以表示为一个以特定节点为根的树形结构。以下面的HTML为例:
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
表示为层级结构:
其中,document 节点表示每个文档的根节点。在这里,根节点的唯一子节点是<html>
元素,我们称之为文档元素(documentElement)。文档元素是文档最外层的元素,所有其他元素都存在于这个元素之内。每个文档只能有一个文档元素。在HTML 页面中,文档元素始终是<html>
元素。
在XML 文档中,则没有这样预定义的元素,任何元素都可能成为文档元素。
HTML 中的每段标记都可以表示为这个树形结构中的一个节点。元素节点表示HTML 元素,属性节点表示属性,文档类型节点表示文档类型,注释节点表示注释。DOM中总共有12 种节点类型,这些类型都继承一种基本类型。
即每个HTML元素,HTML元素内容甚至HTML元素的一个属性都可以被视为一个节点,它们都有不同的Node属性来进行区分。
对节点使用typeof返回是object类型。
对于这样一个元素来说,由以下图示的节点结构组成:
<p title = "a gentle reminder">Don't forget to buy this stuff. </p>
DOM Level 1 描述了名为Node 的接口,这个接口是所有DOM 节点类型都必须实现的。Node 接口在JavaScript 中被实现为Node 类型,在除IE 之外的所有浏览器中都可以直接访问这个类型。在JavaScript中,所有节点类型都继承Node 类型,因此所有类型都共享相同的基本属性和方法。
每个节点都有nodeType 属性,表示该节点的类型。
nodeName 与nodeValue 保存着有关节点的信息。这两个属性的值完全取决于节点类型。
文档中的所有节点都与其他节点有关系。这些关系可以形容为家族关系,相当于把文档树比作家谱。在HTML 中,<body>
元素是<html>
元素的子元素,而<html>
元素则是<body>
元素的父元素。<head>
元素是<body>
元素的同胞元素,因为它们有共同的父元素<html>
。
每个节点都有一个childNodes
属性,其中包含一个NodeList
的实例。NodeList 是一个类数组对象,用于存储可以按位置存取的有序节点。注意,NodeList 并不是Array 的实例,但可以使用中括号访问它的值,而且它也有length 属性。NodeList 对象独特的地方在于,它其实是一个对DOM结构的查询,因此DOM 结构的变化会自动地在NodeList 中反映出来。NodeList 是实时的活动对象,而不是第一次访问时所获得内容的快照。
每个节点都有一个parentNode
属性,指向其DOM 树中的父元素。childNodes 中的所有节点都有同一个父元素,因此它们的parentNode 属性都指向同一个节点。此外,childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点。
使用previousSibling
和nextSibling
可以在这个列表的节点间导航。这个列表中第一个节点的previousSibling 属性是null,最后一个节点的nextSibling 属性也是null。
父节点和它的第一个及最后一个子节点也有专门属性:firstChild
和lastChild
分别指向childNodes 中的第一个和最后一个子节点。
上述这些节点之间的关系为在文档树的节点之间导航提供了方便。下图形象地展示了这些关系。
通过这些关系指针,几乎可以访问到文档树中的任何节点。
hasChildNodes()
方法用于判断子节点是否存在,如果返回true 则说明节点有一个或多个子节点。
还有一个所有节点都共享的关系。ownerDocument
属性是一个指向代表整个文档的文档节点的指针。这个属性为迅速访问文档节点提供了便利,因为无需在文档结构中逐层上溯了。
appendChild()
,用于在childNodes 列表末尾添加节点。添加新节点会更新相关的关系指针,包括父节点和之前的最后一个子节点。appendChild()
方法返回新添加的节点。let returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); // true
alert(someNode.lastChild == newNode); // true
如果把文档中已经存在的节点传给appendChild(),则这个节点会从之前的位置被转移到新位置。即使DOM 树通过各种关系指针维系,一个节点也不会在文档中同时出现在两个或更多个地方。
insertBefore()
方法。这个方法接收两个参数:要插入的节点和参照节点。调用这个方法后,要插入的节点会变成参照节点的前一个同胞节点,并被返回。如果参照节点是null,则insertBefore()
与appendChild()
效果相同,如下面的例子所示:// 作为最后一个子节点插入
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); // true
// 作为新的第一个子节点插入
returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); // true
alert(newNode == someNode.firstChild); // true
// 插入最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true
replaceChild()
方法接收两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档树中完全移除,要插入的节点会取而代之。// 替换第一个子节点
let returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
使用replaceChild()插入一个节点后,所有关系指针都会从被替换的节点复制过来。被替换的节点从技术上说仍然被同一个文档所拥有,但文档中已经没有它的位置。
removeChild()
方法。这个方法接收一个参数,即要移除的节点。被移除的节点会被返回。与replaceChild()
方法一样,通过removeChild()
被移除的节点从技术上说仍然被同一个文档所拥有,但文档中已经没有它的位置。上面介绍的4 个方法都用于操纵某个节点的子元素,也就是说使用它们之前必须先取得父节点(使用前面介绍的parentNode 属性)。并非所有节点类型都有子节点,如果在不支持子节点的节点上调用这些方法,则会导致抛出错误。
即都是在父节点上对子节点进行的操作。
cloneNode()
:返回与调用它的节点一模一样的节点。cloneNode()方法接收一个布尔值参数,表示是否深复制。在传入true 参数时,会进行深复制,即复制节点及其整个子DOM 树。如果传入false,则只会复制调用该方法的节点。复制返回的节点属于文档所有,但尚未指定父节点,所以可称为孤儿节点(orphan)。可以通过appendChild()、insertBefore()或replaceChild()方法把孤儿节点添加到文档中。
normalize()
:这个方法唯一的任务就是处理文档子树中的文本节点。
Document 类型是JavaScript 中表示文档节点的类型。在浏览器中,文档对象document 是HTMLDocument 的实例(HTMLDocument 继承Document),表示整个HTML 页面。document 是window对象的属性,因此是一个全局对象。
即浏览器中每个HTML都会有一个document对象,而这个document对象就是这里HTMLDocument类型的一个实例。
Document 类型可以表示HTML 页面或其他XML 文档,但最常用的还是通过HTMLDocument 的实例取得document 对象。document 对象可用于获取关于页面的信息以及操纵其外观和底层结构。
提供了两个访问子节点的快捷方式:
documentElement
属性,始终指向HTML 页面中的<html>
元素。<html>
<body>
</body>
</html>
这样一个HTML文档,文档只有一个子节点,即<html>
元素。这个元素既可以通过
documentElement 属性获取,也可以通过childNodes 列表访问,如下所示:
let html = document.documentElement; // 取得对<html>的引用
alert(html === document.childNodes[0]); // true
body
属性,直接指向<body>
元素let body = document.body; // 取得对<body>的引用
doctype
属性访问文档中独立的<!doctype>
标签 let doctype = document.doctype; // 取得对<!doctype>的引用
而对于<html>
元素外面的注释,也是文档的子节点,类型是Comment。不过,由于浏览器实现不同,这些注释不一定能被识别,或者表现可能不一致:
<!-- 第一条注释 -->
<html>
<body>
</body>
</html>
<!-- 第二条注释 -->
一般来说,appendChild()、removeChild()和replaceChild()方法不会用在document 对象上。这是因为文档类型(如果存在)是只读的,而且只能有一个Element 类型的子节点(即
<html>
,已经存在了)
document 作为HTMLDocument 的实例,有一些标准Document 对象上所没有的属性。这些属性提供浏览器所加载网页的信息。
title
属性: 包含<title>
元素中的文本,通常显示在浏览器窗口或标签页的标题栏。通过这个属性可以读写页面的标题,修改后的标题也会反映在浏览器标题栏上。但是,修改title 属性并不会改变<title>
元素。 // 读取文档标题
let originalTitle = document.title;
// 修改文档标题
document.title = "New page title";
URL
属性:包含当前页面的完整URL(地址栏中的URL),可以读取但修改不会改变其值。domain
属性:包含页面的域名,可以读取并修改但不能给这个属性设置URL 中不包含的值。使用<frame>
标签时将domain属性设置为相同的值可以使页面之间可以相互访问对方的JavaScript对象实现通信。referrer
属性:返回载入当前文档的来源文档的URL。 如果当前文档不是通过超级链接访问的,则为null。如上面这个页面的referrer属性返回的是谷歌,是通过谷歌搜素进入这个页面,所以谷歌页面就是来源文档。
getElementById()
和getElementsByTagName()
以及getElementsByName()
getElementById()
方法
该方法接收一个参数,即要获取元素的ID,如果找到了则返回这个元素,如果没找到则返回null。参数ID 必须跟元素在页面中的id 属性值完全匹配,包括大小写。
<div id="myDiv">Some text</div>
可以使用如下代码取得这个元素:
let div = document.getElementById("myDiv"); // 取得对这个<div>元素的引用
如果页面中存在多个具有相同ID 的元素,则getElementById()返回在文档中出现的第一个元素。
getElementsByTagName()
方法
这个方法接收一个参数,即要获取元素的标签名,返回包含零个或多个元素的NodeList。在HTML 文档中,这个方法返回一个HTMLCollection 对象。
可以通过中括号或item()方法从HTMLCollection中取得特定的元素,并且有length属性:
let images = document.getElementsByTagName("img");
alert(images.length); // 图片数量
alert(images[0].src); // 第一张图片的src 属性
alert(images.item(0).src); // 同上
HTMLCollection 对象还有一个额外的方法namedItem()
,可通过标签的name
属性取得某一项的引用。例如,假设页面中包含如下的元素:
<img src="myimage.gif" name="myImage">
那么像这样从images 中取得对这个<img>
元素的引用:
let myImage = images.namedItem("myImage");
let myImage = images.namedItem("myImage");
对HTMLCollection 对象而言,中括号既可以接收数值索引,也可以接收字符串索引。而在后台,数值索引会调用item()
,字符串索引会调用namedItem()
。
要取得文档中的所有元素,可以给getElementsByTagName()
传入*
。在JavaScript 和CSS 中,*
一般被认为是匹配一切的字符。来看下面的例子:
let allElements = document.getElementsByTagName("*");
这行代码可以返回包含页面中所有元素的HTMLCollection 对象,顺序就是它们在页面中出现的顺序。因此第一项是<html>
元素,第二项是<head>
元素,以此类推。
getElementsByName()
方法
这个方法会返回具有给定name 属性的所有元素。getElementsByName()方法最常用于单选按钮,因为同一字段的单选按钮必须具有相同的name 属性才能确保把正确的值发送给服务器。同样是返回HTMLCollection对象。
document 对象上还暴露了几个特殊集合,这些集合也都是HTMLCollection 的实例。这些集合是访问文档中公共部分的快捷方式:
document.anchors
包含文档中所有带name
属性的<a>
元素。document.forms
包含文档中所有<form>
元素。document.images
包含文档中所有<img>
元素。document.links
包含文档中所有带href
属性的<a>
元素。所有HTMLCollection 对象的内容都会实时更新以符合当前文档的内容。
向网页输出流中写入内容:4 个方法:write()
、writeln()
、open()
和close()
。
write()
和writeln()
方法都接收一个字符串参数,可以将这个字符串写入网页中。write()
简单地写入文本,而writeln()
还会在字符串末尾追加一个换行符
(\n)。这两个方法可以用来在页面加载期间向页面中动态添加内容:
<body>
<p>The current date and time is:
<script type="text/javascript"> document.write("<strong>" + (new Date()).toString() + "</strong>"); </script>
</p>
</body>
这个例子会在页面加载过程中输出当前日期和时间,并且会创建一个<strong>
元素。
如果是在页面加载完之后再调用document.write(),则输出的内容会重写整个页面。网页显示会变为这个方法中的内容,重写了整个内容,相等于载入了一份新的html文件。
open()
和close()
方法分别用于打开和关闭网页输出流,即终止文档流模型的读取显示。
Element 类型的节点具有以下特征:
可以通过nodeName
或tagName
属性来获取元素的标签名。HTML中获取的元素标签名始终以全大写表示。在XML(包括XHTML)中,标签名始终与源代码中的大小写一致。如果不确定脚本是在HTML 文档还是XML 文档中运行,最好将标签名转换为小写形式。
最好用下面的第二种方式使用:
if (element.tagName == "div"){
// 不要这样做,可能出错!
// do something here
}
if (element.tagName.toLowerCase() == "div"