截取自读书频道:
http://book.csdn.net/bookfiles/110/index.htm
6.1 什么是DOM?
在开始详细介绍什么是DOM之前,你首先要了解是什么促使了它的诞生。尽管DOM很大程度上受到浏览器中动态HTML发展的影响,但W3C还是将它最先应用于XML。
6.1.1 XML简介
XML(可扩展标记语言)是从称为SGML(标准通用标记语言)的更加古老的语言派生出来的。SGML的主要目的是定义使用标签来表示数据的标记语言的语法。
XML去掉了之前令许多开发人员头疼的SGML的随意语法。
1 任何的起始标签都必须有一个结束标签。
2 可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。这种语法是在大于符号之前紧跟一个斜线(/),例如
4 所有的特性都必须有值。
5 所有的特性都必须在值的周围加上双引号。
xml的主要目的是使用文本以结构化的方式来表示数据。在某些方面,XML文件也类似于数据库,提供数据的结构化视图。
每个XML文档都由XML序言开始,在前面的代码中的第一行便是XML序言,。
第二行代码,
然后其中的具体内容就可能包括注释,处理指令,代码.
6.1.2 针对XML的API
将XML定义为一种语言之后,就出现了使用常见的编程语言(如Java)来同时表现和处理XML代码的需求。
首先出现的是Java上的SAX(Simple API for XML)项目。SAX提供了一个基于事件的XML解析的API。从其本质上来说,SAX解析器从文件的开头出发,从前向后解析,每当遇到起始标签或者结束标签、特性、文本或者其他的XML语法时,就会触发一个事件。然后,当事件发生时,具体要怎么做就由开发人员决定。
DOM是针对XML的基于树的API。它关注的不仅仅是解析XML代码,而是使用一系列互相关联的对象来表示这些代码,而这些对象可以被修改且无需重新解析代码就能直接访问它们。DOM是语言无关的API,他没有与某种语言绑定。
6.1.3 节点的层次
DOM定义了Node的接口以及许多种节点类型来表示XML节点的多个方面:
1 Document——最顶层的节点,所有的其他节点都是附属于它的。
2 DocumentType——DTD引用(使用语法)的对象表现形式,例如。它不能包含子节点。
3 DocumentFragment——可以像Document一样来保存其他节点。
4 Element——表示起始标签和结束标签之间的内容,例如
5 Attr——代表一对特性名和特性值。这个节点类型不能包含子节点。
6 Text——代表XML文档中的在起始标签和结束标签之间,或者CData Section内包含的普通文本。这个节点类型不能包含子节点。
8 CDataSection——的对象表现形式。这个节点类型仅能包含文本节点Text作为子节点。
9 Entity——表示在DTD中的一个实体定义,例如。这个节点类型不能包含子节点。
10 EntityReference——代表一个实体引用,例如"。这个节点类型不能包含子节点。
11 ProcessingInstruction——代表一个PI。这个节点类型不能包含子节点。
12 Comment——代表XML注释。这个节点类型不能包含子节点。
13 Notation——代表在DTD中定义的记号。这个很少用到,所以在本书中不会讨论。
Node接口定义了对应不同节点类型的12个常量(它们会在即将讨论的nodeType特性中使用到):
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)
Node接口也定义了一些所有节点类型都包含的特性和方法。我们在下面的表格中列出了这些特性和方法:
特性/方法 |
类型/返回类型 |
说 明 |
nodeName |
String |
节点的名字;根据节点的类型而定义 |
nodeValue |
String |
节点的值;根据节点的类型而定义 |
nodeType |
Number |
节点的类型常量值之一 |
ownerDocument |
Document |
指向这个节点所属的文档 |
firstChild |
Node |
指向在childNodes列表中的第一个节点 |
lastChild |
Node |
指向在childNodes列表中的最后一个节点 |
childNodes |
NodeList |
所有子节点的列表 |
previousSibling |
Node |
指向前一个兄弟节点;如果这个节点就是第一个兄弟节点,那么该值为null |
nextSibling |
Node |
指向后一个兄弟节点;如果这个节点就是最后一个兄弟节点,那么该值为null |
hasChildNodes() |
Boolean |
当childNodes包含一个或多个节点时,返回真 |
attributes |
NamedNodeMap |
包含了代表一个元素的特性的Attr对象;仅用于Element节点 |
appendChild(node) |
Node |
将node添加到childNodes的末尾 |
removeChild(node) |
Node |
从childNodes中删除node |
replaceChild |
Node |
将childNodes中的oldnode替换成newnode |
insertBefore |
Node |
在childNodes中的refnode之前插入newnode |
除节点外,DOM还定义了一些助手对象,它们可以和节点一起使用,但不是DOM文档必有的部分。
1 NodeList——节点数组,按照数值进行索引;用来表示一个元素的子节点。
2 NamedNodeMap——同时用数值和名字进行索引的节点表;用于表示元素特性。
6.1.4 特定语言的DOM
任何基于XML的语言,如XHTML和SVG,因为它们从技术上来说还是XML,仍然可以利用刚刚介绍的核心DOM。然而,很多语言会继续定义它们自己的DOM来扩展XML核心以提供语言的特色功能。
6.2 对DOM的支持
并不是所有的浏览器对 DOM 的支持都一样。一般来说,Mozilla 对DOM 标准支持最好,这个领域中落在最后的是IE,它对DOM Level 1的实现都还不完整,尚有很多方面有待完善。
6.3 使用DOM
document 对象是BOM 的一部分,同时也是HTML DOM 的 HTMLDocument 对象的一种表现形式,反过来说,它也是XML DOM Document 对象在下面的几节中考虑下面的HTML页面:
要访问元素(你应该明白这是该文件的document元素),你可以使用document的documentElement特性:
var oHtml = document.documentElement;
现在变量oHtml包含一个表示的HTMLElement对象。如果你想取得和元素,下面的可以实现:
也可以使用childNodes特性来完成同样的工作。只需把它当成普通的JavaScript Array,使用方括号标记:
你还可以通过使用childNodes.length特性来获取子节点的数量:
注意方括号标记其实是NodeList在JavaScript中的简便实现。实际上正式的从childNodes列表中获取子节点的方法是使用item()方法:
HTML DOM页定义了document.body作为指向元素的指针:
有了oHtml、oHead和oBody这三个变量,就可以先尝试确定它们之间的关系:
这一小段代码测试并验证了oBody和oHead的parentNode特性都是指向oHtml变量,同时使用previousSibling和nextSibling特性来建立它们之间的关系。最后一行确认了oHead的ownerDocument特性事实上是指向该文档。
6.3.2 检测节点类型
我们可以通过使用nodeType特性检验节点类型:
这个例子中,document.nodeType返回9,等于Node.DOCUMENT_NODE;同时document. documentElement.nodeType返回1,等于Node.ELEMENT_NODE。
也可以用Node常量来匹配这些值:
这段代码可以在Mozilla 1.0+、Opera 7.0+和Safari 1.0+上正常运行。不幸的是,IE不支持这些常量,所以这些代码在IE上会产生错误。所幸,可以通过定义匹配节点类型的常量来纠正这种情况,正如下面这样:
6.3.3 处理特性
只有Element节点才能有特性。Element节点的attributes属性其实是NamedNodeMap,它提供一些用于访问和处理其内容的方法:
1 getNamedItem(name)——返回nodeName属性值等于name的节点;
2 removeNamedItem(name)——删除nodeName属性值等于name的节点;
3 setNamedItem(node)——将node添加到列表中,按其nodeName属性进行索引;
4 item(pos)——像NodeList一样,返回在位置pos的节点;
NamedNodeMap对象也有一个length属性来指示它所包含的节点的数量。
当NamedNodeMap用于表示特性时,其中每个节点都是Attr节点,它的nodeName属性被设置为特性名称,而nodeValue属性被设置为特性的值。例如,假设有这样一个元素:
同时,假设变量oP包含指向这个元素的一个引用。于是可以这样访问id特性的值:
还可以通过给nodeValue属性赋新值来改变id特性:
Attr节点也有一个完全等同于(同时也完全同步于)nodeValue属性的value属性,并且有name属性和nodeName属性保持同步。我们可以随意使用这些属性来修改或变更特性。
因为这个方法有些累赘,DOM又定义了三个元素方法来帮助访问特性:
q getAttribute(name)——等于attributes.getNamedItem(name).value;
q setAttribute(name, newvalue)——等于attribute.getNamedItem(name).value = newvalue;
q removeAttribute(name)——等于attributes.removeNamedItem(name)。
这些方法相当有用,可以直接处理特性值,完全地隐藏Attr节点。所以,要获取前面用的的id特性,只需这样做:
var sId = oP.getAttribute("id");
同时要更改ID,可以这样做
正如你所看到的,这些方法要比使用NamedNodeMap的方法简洁得多。
6.3.4 访问指定节点
1. getElementsByTagName()
tagName特性总是等于小于号之后紧随的名称
下一行代码返回文档中所有元素的列表:
var oImgs = document.getElementsByTagName("img");
在把所有图形都存于oImgs后,只需使用方括号标记或者item()方法(getElementsByTag- Name()返回一个和childNodes一样的NodeList),就可以像访问子节点那样逐个访问这些节点了:
2. getElementsByName()
HTML DOM 定义了 getElementsByName() ,它用来获取所有 name 特性等于指定值的元素的。3. getElementById()
这是 HTML DOM 定义的第二种方法,它将返回 id 特性等于指定值的元素。在 HTML 中, id 特性是唯一的——这意味着没有两个元素可以共享同一个 id 。6.3.5 创建和操作节点
1. 创建新节点
DOM Document (文档)中有一些方法用于创建不同类型的节点面的表格列出了包含在DOM Level 1中的方法,并列出不同的浏览器是否支持项。
方 法 |
描 述 |
IE |
MOZ |
OP |
SAF |
createAttribute |
用给定名称name创建特性节点 |
× |
× |
× |
- |
createCDATASection |
用包含文本text的文本子节点创建一个CDATA Section |
- |
× |
- |
- |
createComment(text) |
创建包含文本text的注释节点 |
× |
× |
× |
× |
(续)
方 法 |
描 述 |
IE |
MOZ |
OP |
SAF |
createDocument |
创建文档碎片节点 |
× |
× |
× |
× |
createElement |
创建标签名为tagname的元素 |
× |
× |
× |
× |
createEntity |
创建给定名称的实体引用节点 |
- |
× |
- |
- |
createProcessing |
创建包含给定target和data的PI节点 |
- |
× |
- |
- |
createTextNode(text) |
创建包含文本text的文本节点 |
× |
× |
× |
× |
最常用到的几个方法是:createDocumentFragment()、createElement()和createText- Node();
2. createElement()、createTextNode()、appendChild()
假设有如下HTML页面:
现在想使用DOM来添加下列代码到上面这个页面中:
这里可以使用createElement()和createTextNode()来达到目的。下面是实现步骤:
首先,创建元素:
var oP = document.createElement("p");
第二,创建文本节点:
下一步,把文本节点加入到元素中。可以用在本章前面简要提到的appendChild()方法来完成这个任务。每种节点类型都有appendChild()方法,它的用途是将给定的节点添加到某个节点的childNodes列表的尾部。在这个例子中,应将文本节点追加到元素中:
oP.appendChild(oText);
不过还没完成全部操作。已经创建了一个元素和一个文本节点,并且将它们关联在一起了,但这个元素在文档中仍然没有一席之地。要实际可见,必须将这个元素附加到document.body元素或者其中任意子节点上。然后,可以再次使用appendChild()方法:
document.body.appendChild(oP);
在这里,我必须谨慎地告诉你所有的DOM操作必须在页面完全载入之后才能进行。当页面正在载入时,要向DOM插入相关代码是不可能的,因为在页面完全下载到客户端机器之前,是无法完全构建DOM树的。因为这个原因,必须使用onload事件句柄来执行所有的代码。
3. removeChild()、replaceChild()和insertBefore()
如果有个已经包含"Hello World!"消息的页面,要把这个消息删除,可以使用类似下面方法:
replaceChild()方法有两个参数:被添加的节点和被替换的节点。
可能想让两个消息同时出现。如果想让新消息出现在老消息之后,只要使用appendChild()方法:
insertBefore()方法。这个方法接受两个参数:要添加的节点和插在哪个节点之前。
4. createDocumentFragment()
一旦把节点添加到document.body(或者它的后代节点)中,页面就会更新并反映出这个变化。对于少量的更新,这是很好的,就像在前面的例子中那样。然而,当要向document添加大量数据时,如果逐个添加这些变动,这个过程有可能会十分缓慢。为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次性添加到document中。
假设你想创建十个新段落。若使用前面学到的方法,可能会写出这种代码:
这段代码运行良好,但问题是它调用了十次document.body.appendChild(),每次都要产生一次页面刷新。这时,文档碎片就十分有用:
在这段代码中,每个新的 元素都被添加到文档碎片中。然后,这个碎片被作为参数传递给 appendChild() 。这里对 appendChild() 的调用实际上并不是把文档碎片节点本身追加到 元素中;而是仅仅追加碎片中的子节点。
6.4 HTML DOM特征功能
核心DOM的特性和方法是通用的,是为了在各种情况下操作所有XML文档而设计的。HTML DOM的特性和方法是在专门针对HTML的同时也让一些DOM操作更加简便。这包括将特性作为属性进行访问的能力,以及特定于元素的属性和方法,这些扩展可以完成一些常见的任务,例如搭建表格,更加简便快速。
6.4.1 让特性像属性一样
大部分情况下,HTML DOM元素中包含的所有特性都是可作为属性。例如,假设有如下图像元素:
如果要使用核心的DOM来获取和设置src和border特性,那么要用getAttribute()和setAttribute()方法:
然而,使用HTML DOM,可以使用同样名称的属性来获取和设置这些值:
唯一的特性名和属性名不一样的特例是class特性,它是用来指定应用于某个元素的一个CSS类,例如:
因为class在ECMAScript中是一个保留字,在JavaScript中,它不能被作为变量名、属性名或者函数名。于是,相应的属性名就变成className:
6.4.2 table 方法
假设想使用DOM来创建如下的HTML表格:
给元素添加了以下内容:
q caption——指向
q tBodies——元素的集合;
q tFoot——指向元素(如果存在);
q tHead——指向元素(如果存在);
q rows——表格中所有行的集合;
q createTHead()——创建元素并将其放入表格;
q createTFoot()——创建元素并将其放入表格;
q createCaption()——创建
q deleteTHead()——删除元素;
q deleteTFoot()——删除元素;
q deleteCaption()——删除
q deleteRow(position)——删除指定位置上的行;
q insertRow(position)——在rows集合中的指定位置上插入一个新行。
元素添加了以下内容:
q rows——中所有行的集合;
q deleteRow(position)——删除指定位置上的行;
q insertRow(position)——在rows集合中的指定位置上插入一个新行。
元素中添加了以下内容:
q cells——元素中所有的单元格的集合;
q deleteCell(position)——删除给定位置上的单元格;
q insertCell(position)——在cells集合的给定位置上插入一个新的单元格。
6.5 遍历DOM
到目前为止,我们讨论的功能都仅仅是 DOM Level 1 的部分。本节将介绍一些DOM Level 2 的功能,尤其是和遍历DOM 文档相关的DOM Level 2 遍历(traversal )和范围(range )规范中的对象。这些功能只有在Mozilla 和Konqueror/Safari 中才有第一个有关的对象是NodeIterator,用它可以对DOM树进行深度优先的搜索,如果要查找页面中某个特定类型的信息(或者元素),这是相当有用的。要理解NodeIterator到底做了什么,考虑下面的HTML页面:
这个页面可以转换成由图6-2表示的DOM树。
图 6-2
当使用NodeIterator时,可以从document元素()开始,按照一种系统的路径——也就是大家熟知的深度优先搜索——遍历整个DOM树。在这种搜索方式中,遍历从父节点开始,到子节点,再到子节点的子节点,如此继续,尽可能往深入,直到不能再往下走为止。然后,遍历过程向上回退一层,并进入下一个子节点。例如,在前面展示的DOM树中,遍历过程在回退到前,先访问了,然后,然后
图 6-3
最佳的思考深度优先搜索的方法就是从第一个节点左边的第一个节点起沿着树的最外沿画线。只要这条线从左侧经过某个节点,那么这个节点就是搜索中下一个出现的节点(图6-3中的粗线代表了这条线)。
要创建NodeIterator对象,请使用document对象的createNodeIterator()方法。这个方法接受四个参数:
(1) root——从树中开始搜索的那个节点。
(2) whatToShow——一个数值代码,代表哪些节点需要访问。
(3) filter——NodeFilter对象,用来决定需要忽略哪些节点。
(4) entityReferenceExpansion——布尔值,表示是否需要扩展实体引用。
通过应用下列一个或多个常量,whatToShow参数可以决定哪些节点可以访问:
q NodeFilter.SHOW_ALL——显示所有的节点类型;
q NodeFilter.SHOW_ELEMENT——显示元素节点;
q NodeFilter.SHOW_ATTRIBUTE——显示特性节点;
q NodeFilter.SHOW_TEXT——显示文本节点;
q NodeFilter.SHOW_CDATA_SECTION——显示CData section节点;
q NodeFilter.SHOW_ENTITY_REFERENCE——显示实体引用节点;
q NodeFilter.SHOW_ENTITY——显示实体节点;
q NodeFilter.SHOW_PROCESSING_INSTRUCTION——显示PI节点;
q NodeFilter.SHOW_COMMENT——显示注释节点;
q NodeFilter.SHOW_DOCUMENT——显示文档节点;
q NodeFilter.SHOW_DOCUMENT_TYPE——显示文档类型节点;
q NodeFilter.SHOW_DOCUMENT_FRAGMENT——显示文档碎片节点;
q NodeFilter.SHOW_NOTATION——显示记号节点。
可以通过使用二进制或操作符来组合多个值:
createNodeIterator()的filter(过滤器)参数可以指定一个自定义的NodeFilter对象,但是如果不想使用它的话,也可以留空(null)。
要创建最简单的访问所有节点类型的NodeIterator对象,可以使用下面的代码:
要在搜索过程中前进或者后退,可以使用nextNode()和previousNode()方法:
假设想列出某个区域内指定 中包含的所有元素。下列代码可以完成这个任务:
当点击了按钮后,将使用包含在div1中的元素的标签名来填充:
P
B
UL
LI
LI
LI
但假设不想在结果中包含元素。这就不能仅使用whatToShow参数来完成。这种情况下,你需要自定义一个NodeFilter对象。
NodeFilter对象只有一个方法:acceptNode()。如果应该访问给定的节点,那么该方法返回NodeFilter.FILTER_ACCEPT;如果不应该访问给定节点,则返回NodeFilter.FILTER_REJECT。然而,不能使用NodeFilter类来创建这个对象,因为这个类是一个抽象类。在Java或一些其他的语言中,必须重新定义一个NodeFilter的子类,不过,因为这是在JavaScript中,就不必这么做了。
现在,只要创建任意一个有acceptNode()方法的对象,就可以将它传给createNodeIter- ator()方法,如下所示:
若要禁止元素节点,只需检查tagName属性,如果它等于"P",就返回NodeFilter. FILTER_REJECT:
如果将这段代码包含在前面的例子中,代码如下:
当这次再点击按钮时,中就会出现下列内容:
UL
LI
LI
LI
注意"P"和"B"都没有出现在列表中。这是因为排除元素后,不仅从迭代搜索中去掉了它,也去掉了它的所有后代节点。因为是的一个子节点,所以它也被跳过。
NodeIterator对象展示了一种有序的自顶向下遍历整个DOM树(或者仅仅其中一部分)的方式。然而可能想遍历到树的特定区域时,再看看某个节点的兄弟节点或者子节点。如果是这种情况,可以使用TreeWalker。
6.5.2 TreeWalker
TreeWalker有点像NodeIterator的大哥:它有NodeIterator所有的功能(nextNode()和previousNode()),并且添加了一些遍历方法:
q parentNode()——进入当前节点的父节点;
q firstChild()——进入当前节点的第一个子节点;
q lastChild()——进入当前节点的最后一个节点;
q nextSibling()——进入当前节点的下一个兄弟节点;
q previousSibling()——进入当前节点的前一个兄弟节点。
要开始使用TreeWalker,其实完全可以像使用NodeIterator那样,只要把createNode- Iterator()的调用改为调用createTreeWalker(),这个函数接受同样的参数:
假设只想访问前面所显示的HTML页面中的元素。可以写一个只接受标签名为"LI"的元素的过滤器,而这里,可以使用TreeWalker来进行更有目的性的遍历:
在这个例子中,创建了TreeWalker并立刻调用了firstChild()方法,将walker指到元素(因为是div1的第一个子节点)。在接下来的一行里调用nextSibling()后,walker指到了的下一个兄弟节点上。然后,调用firstChild()返回到
下的第一个元素。接下来,在循环内部,通过使用nextSibling()方法对剩下的元素进行迭代。
点击按钮后,应该会看到如下输出:
LI
LI
LI
最后要说的是,如果对将要遍历的DOM树的结构有所了解的话,TreeWalker更加有用;而同时,如果并不知道这个结构的话,使用NodeIterator更加实际有效。
6.6 测试与DOM标准的一致性
现在你肯定可以说出DOM的许多方面。正因如此,你需要一种方法来确定给定的DOM实现到底支持DOM的哪些部分。有趣的是,这个对象就叫做implementation。
implementation对象是DOM文档的一个特性,因此,也是浏览器document对象的一部分。implementation唯一的方法是hasFeature(),它接受两个参数:要检查的特征和特征的版本。例如,如果想检查对XML DOM Level 1的支持,可以这样调用:
var bXmlLevel1 = document.implementation.hasFeature("XML", "1.0");
6.7 DOM Level 3
2004 年 4 月, DOM Level 3 作为 W3C 的一个推荐标准被提出。目前为止,还没有哪个浏览器已经完全实现该标准,只有 Mozilla 已实现了一部分