DOM是针对HTML和XML文档的一个API。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
IE中的所有DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM对象与原生的JavaScript对象的行为或活动特点并不一致。
节点层次
<html>
<head>Sample Page</head>
<body>
<p>Hello World!</p>
</body>
</html>
文档节点是每个文档的根节点。这个例子中,文档节点只有一个子节点,即html
元素,我们称之为文档元素。文档元素是文档的最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。在XML中,没有预定义的元素,因此任何元素都可能成为文档元素。
每一段标记都可以通过树中的一个节点来表示。
Node 类型
DOM1级定义了一个Node接口,该接口将由DOM中所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的;除了IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法。
每个节点都有一个nodeType
属性,用于表面节点的类型。节点类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一:
- Node.ELEMENT_NODE(1);
- Node.ATTRIBUTE_NODE(2);
- Node.TEXT_NODE(3);
- Node.CDATE_SECTION_NODE(4);
- Node.ENTITY_REFERENCE_NODE(5);
- Node.ENTITY_NODE(6);
- Node.PROCESSING_INSTRUCTION_NODE(7);
- Node.COMMENT_NODE(8);
- Node.DOCUMENT_NODE(9);
- Node.DOCUMENT_TYPE_NODE(10);
- Node.DOCUMENT_FRAGMENT_NODE(11);
- Node.NOTATION_NODE(12);
if (someNode.nodeType == Node.ELEMENT_NODE) { //在IE中无效
alert("Node is an element.");
}
//由于IE没有公开Node类型的构造函数,因此上面的的代码在IE中会导致错误
if (someNode.nodeType == 1) { //适用于所有的浏览器
alert("Node is an element.");
}
并不是所有节点类型都收到Web浏览器的支持。
nodeName 和 nodeValue 属性
对于元素节点,nodeName中保存的始终是元素的标签名,而nodeValue始终为null。
节点关系
每个节点都有一个childNodes
属性,其中保存着一个NodeList对象。NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
另外,要注意length属性表示的是访问NodeList的那一刻,其中包含的节点数量。可以用Array.prototype.slice()
方法将NodeList对象转化为数组。
//在IE8之前版本无效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
//由于IE8之前的版本将NodeList实现为一个COM对象。要想在IE中将NodeList转化为数组,必须手动枚举所有成员。
function convertToArray (nodes) {
var array = null;
try {
array = Array.prototype.slice.call(nodes, 0); //针对非IE浏览器
} catch (ex) {
array = new Array();
for (var i = 0, len = nodes.length; i < len; i++) {
array.push(nodes[i]);
}
}
return array;
}
每个节点都有一个parentNode
属性,该属性指向文档树中的父节点。包含在childNodes列表中的所有节点都具有相同的父节点。此外,包含在childNodes列表中的每个节点相互之间都是同胞节点。通过使用列表中每个节点的previousSibling
和nextSibling
属性,可以访问同一列表中的其他节点。列表中的第一个节点的previousSibling
属性值为null,而列表中最后一个节点的nextSibling
属性的值同样也为null。
if (someNode.nextSibling === null) {
alert("Last node in the parent`s childNodes list.");
} else if (someNode.previousSibling === null) {
alert("First node in the parent`s childNodes list.");
}
当然,如果只有一个节点,那么该节点的previousSibling
和nextSibling
属性都为null。
父节点的firstChild
和lastChild
属性分别指向其childNodes列表中的第一个和最后一个节点。在只有一个子节点的情况下,firstChild
和lastChild
指向同一个节点。如果没有子节点,那么firstChild
和lastChild
的值均为null。
hasChildNodes()
这个方法在节点包含一个或多个子节点的情况下返回true。
所有节点都有的最后一个属性是ownerDocument
,该属性指向表示整个文档的文档节点。
操作节点
因为关系指针是只读的,所有DOM提供了一些操作节点的方法。
appendChild()
用于向childNodes列表的末尾添加一个节点。更新完成后,appendChild()
返回新增的节点。
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(returnedNode.lastChild == newNode); //true
如果传入到appendChild()
中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置。
因此,如果在调用appendChild()
时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。
//someNode有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
insertBefore()
方法可以把节点放在childNodes列表中某个特定的位置上。这个方法接收两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参考节点的前一个同胞节点(previousSibling),同时被方法返回。如果参考节点是null,则insertBefore()
与appendChild()
执行相同的操作。
//插入后成为最后一个子节点
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); //true
//插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode, childNodes.length - 2]); //true
replaceChild()
方法接收两个参数:要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。
//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
在使用replaceChild()
插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档中已经没有了自己的位置。
removeChild()
方法接收一个参数,既要移除的节点。被移除的节点将成为方法的返回值。
//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);
通过removeChild()
移除的节点仍然为文档所有,只不过在文档中已经没有了自己的位置。
如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。
其他方法
有两个方法是所有类型的节点都有的:
-
cloneNode()
,用于创建调用这个方法的节点的一个完全相同的副本。接收一个布尔值参数,表示是否执行深复制。在参数为true时,执行深复制,也就是复制节点及其整个子节点树;在参数为false,执行浅复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它制定父节点。 -
normalize()
,这个方法唯一的作用就是处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者连接出现两个文本节点的情况。当某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。
cloneNode()
方法不会复制添加到DOM节点中的JavaScript属性,例如事件处理程序等。这个方法只复制特性、(在明确指定的情况下也复制)子节点,其他一切都不会复制。IE在此存在一个BUG,即它会复制事件处理程序,所以我们建议在复制之前最好先移除事件处理程序。
Document 类型
在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问。
Document节点具有下列特性:
- nodeType的值为9
- nodeName的值为“#document”
- nodeValue的值为null
- parentNode的值为null
- ownerDocument的值为null
- 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。
Document类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应用还是作为HTMLDocument实例的document对象。
在Safari、Firefox、Chrome和Opera中,可以通过脚本访问Document类型的构造函数和原型。但在所有浏览器中都可以访问HTMLDocument类型的构造函数和原型,包括IE8及后续版本。
文档的子节点
document
对象的documentElement
属性始终指向HTML页面中的<html>元素。
<html>
<body>
</body>
</html>
var html = document.documentElement; //取得对<html>的引用
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true
document
对象还有一个body
属性,直接指向<body>元素。
var body = document.body; //取得对<body>的引用
所有浏览器都支持document.documentElement和document.body属性。
Document另一个可能的子节点是DocumentType。通常将<!DOCTYPE>标签看出一个与文档其他部分不同的实体,可以通过doctype
属性(在浏览器中是document.doctype)来访问它的信息。
var doctype = document.doctype; //取得对<!DOCTYPE>的引用
浏览器对document.doctype
的支持差别很大:
- IE8及之前的版本:如果存在文档类型声明,会将其错误地解释为一个注释并把它当作Comment节点;而
document.doctype
的值始终为null。 - IE9+及Firefox:如果存在文档类型什么,则将其作为文档的第一个子节点;
document.doctype
是一个DocumentType节点,也可以通过document.firstChild或document.childNodes[0]访问同一个节点。 - Safari、Chrome和Opera:如果存在文档类型声明,则将其解析,但不作为文档的子节点。
document.doctype
是一个DocumentType节点,但该节点不会出现在document.childNodes
中。
从技术上说,出现在<html>元素外部的注释应该算是文档的子节点。然而,不同的浏览器在是否解析这些注释以及能否正确处理它们等方面,也存在很大差异。
<!-- 第一条注释 -->
<html>
<body>
</body>
</html>
<!-- 第二条注释 -->
从逻辑上讲,我们会认为document.childNodes
中应该包含3项,但是实际上存在差异。
- IE8及之前版本、Safari3.1及更高版本、Opera和Chrome只为第一条注释创建节点,不为第二条注释创建节点。结果,第一条注释就会成为
document.childNodes
中的第一个子节点。 - IE9及更高版本会将第一条注释创建为
document.childNodes
中的一个注释节点,也会将第二条注释创建为document.childNodes
中的注释子节点。 - Firefox以及Safari3.1之前的版本完全忽略这两条注释。
文档信息
document对象还有一些标准的Document对象所没有的属性。
title属性包含着<title>元素中的文本。
//取得文档标题
var originalTitle = document.title;
//设置文档标题
document.title = "New page title";
URL属性中包含页面完整的URL(即地址栏中显示的URL)。
domain属性中只包含页面的域名。
referrer属性中保存着链接到当前页面的那个页面的URL。在没有来源的情况下,referrer属性中可能会包含空字符串。
//取得完整的URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得来源页面的URL
var referrer = document.referrer;
在这3个属性中,只有domain
是可以设置的,但也有安全方面的限制。
//假设页面来自p2p.wrox.com域
document.domain = "wrox.com"; //成功
document.domain = "nczo.net"; //出错
当页面中包含来自其他子域的框架或内嵌框架时,能够设置document.domain
就非常方便了。由于跨域安全限制,来自不同子域的页面无法通过JavaScript通信。而通过将每个页面的document.domain
设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了。
浏览器对domain属性还有一个限制,即域名一开始是松散的,那么不能将它再设置为紧绷的。
//假设页面来自于p2p.wrox.com域
document.domain = "wrox.com"; //松散的,成功
document.domain = "p2p.wrox.com"; //紧绷的,出错
所有浏览器中都存在这一限制,但IE8是实现这一限制的最早IE版本。
查找元素
getElementById()
,接受一个参数:要取得的元素的ID。如果找到则返回该元素,没找到则返回null。这里的ID必须与页面中元素的id特性(attribute)匹配,包括大小写。
<div id="myDiv">Some text</div>
var div = document.getElementById("myDiv"); //取得<div>元素的引用
var div = document.getElementById("mydiv"); //无效的ID (在IE7及更早的版本可以)
IE8及较低版本不区分ID的大小写。
如果页面中多个元素的ID相同,getElementById()
只返回文档中第一个出现的元素。
IE7及较低版本还为此方法添加了一个怪癖:name特性与给定ID匹配的表单元素(<input><textarea><button><select>)也会被方法返回。
<input type="text" name="myElement" value="Text field">
<div id="myElement">A div</div>
<!-- document.getElementById("myElement")会返回<input>元素 -->
getElementByTagName()
,接受一个参数,即要取得的元素的标签名,返回的是包含零或多个元素的NodeList。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个“动态”集合,该对象与NodeList非常相似。
var images = document.getElementById("img");
alert(images.length); //输出图像的数量
alert(images[0].src); //输出第一个图像元素的src特性
alert(images.item(0).src); //输出第一个图像元素的src特性
HTMLCollection对象有一个方法namedItem()
,通过元素的name特性取得集合中的项。
<img src="myimage.gif" name="myImage">
var myImage = images.namedItem("myImage");
HTMLCollection还支持按名称访问项:
var myImages = images["myImage"];
对于HTMLCollection,在后台对数值索引就会调用item()
,而对于字符串索引就会调用namedItem()
。
//取得文档中的所有元素
var allElements = document.getElementsByTagName("*");
//由于在IE将注释(comment)实现为元素(element),因此在IE中调用getElementByTagName("*")将会返回所有的注释节点。
getElementsByName()
方法返回带有给定name特殊的所有元素。返回的也是一个HTMLCollection。
特殊集合
除了属性和方法,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象。
- document.anchors 包含文档中所有带name特性的<a>元素。
- document.applets 包含文档中所有的<applet>元素。
- document.forms 包含文档中所有的<form>元素,等于document.getElementsByTagName("form")
- document.images 包含了文档中所有的<img>元素
- document.links 包含了文档中所有的带href特性的<a>元素
上面集合始终可以通过HTMLDocument对象访问到,而且和HTMLCollection对象类似,集合中的项也会随着当前文档内容的更新而更新。
DOM 一致性检测
document.implementation
属性提供了浏览器实现DOM的哪些部分的相应信息和功能的对象。
DOM1级只为document.implementation
规定了一个方法hasFeature()
。该方法接受两个参数:要检测的DOM功能的名称及版本号。支持的话就返回true。
var hasXmlDom = document.implementation.hasFeature("XML","1.0");
DOM功能.png
有时候返回true也不意味着一定和规范一致。
文档写入
write()
和writeln()
方法都接受一个字符串参数,即要写入到输出流中的文本。write()
会原样写入,而writeln()
会在字符串的末尾添加一个换行符(\n)。
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<p>The current date and time is :
<script>
document.write("<strong>" + (new Date()).toString() + "</strong>");
</script>
</p>
</body>
</html>
open()
和close()
分别用于打开和关闭网页的输入流。
Element类型
Element类型用于表现XML或HTML元素,提供了对元素标签名、子节点及特性的访问。Element节点具有以下特征:
- nodeType的值为1;
- nodeName的值为元素的标签名;
- nodeValue的值为null
- parentNode可能是Document或Element;
- 其子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference
要访问元素的标签名,可以使用nodeName
或tagName
(主要为了清晰起见)属性。
<div id="myDiv"></div>
var div = document.getElementById("myDiv");
alert(div.tagName); //"DIV"
alert(div.tagName == div.nodeName); //true
在HTML中,标签名始终是全部大写;而在XML中,标签名则始终会与源代码一样。
HTML元素
网友评论