http://www.17xie.com/read-60506.html
在上一次Web开发领域的技术浪潮中,文档对象模型(Document Object Model,DOM)无疑是开发者能用来提升用户体验的最重要技术之一。
用DOM脚本编程为页面增加分离式(unobtrusive)JavaScript,既能为你的用户提供各种各样时髦的增效,同时又不会影响那些使用不支持或禁用了JavaScript的浏览器的人们。此外,DOM脚本编程还能精细地分离和轻松地管理你的代码。
幸运的是,所有的现代浏览器都支持DOM和当前HTML文档的内置HTML DOM表达。有了这些后就可以通过JavaScript轻松访问它们,从而为现代Web开发者提供了巨大的便利。理解如何使用并尽情发挥这些技术,你的下一个Web应用程序将会领先一筹。
这一章探讨关于DOM的一系列话题。假定你是DOM新手,我们将从基础开始,并涉及所有重要的概念。而对已经熟悉了DOM的读者,我保证能提供一批你会喜欢的高级技术。
2.1 DOM简介
DOM是一个表达XML文档的标准(由W3C制定的)。它未必是最快的方式,也未必是最轻量级的或者最容易使用的,但确是应用最广泛的,大部分Web开发的编程语言(比如Java、Perl、PHP、 Ruby、Python和JavaScript)都提供了相应的DOM实现。DOM给开发者提供了一种定位XML层级结构的直观方法。就算你尚未完全熟悉 XML也没关系,HTML文档(实际上从浏览器角度来说就是XML文档)已经有一个可用的DOM表达,你会为此而备受鼓舞。
2.2 遍历DOM
可以把XML的DOM表达方式看作是一棵导航树。一切术语都跟家谱术语(父、子、兄弟)类似。而与家谱的不同之处在于,XML文档从一个独立的根节点(称作文档元素,document element)开始,它包含指向子节点的指针。每一个子节点都包含指针指向它的父节点、相邻节点和子节点。
DOM还使用了一些特殊的术语来描述XML树里的对象种类。DOM树中的每个对象都是一个节点(node),每个节点可以有一个类型(type),比如元素、文本或者文档。要进一步学习下去,我们必须了解 DOM文档是如何表现和如何定位的。请参考以下简单的HTML片段,让我们来研究研究它的DOM结构是如何工作的:
<p><strong>Hello</strong> how are you doing?</p>
这个片段的每一部分被分解为独立的DOM节点指针,指向其亲戚(父、子、兄弟)。使用图谱来表示的话,它应该如图5-1所示:片断的每部分(圆角框描述元素,直角框描述文本节点)和它的引用。
图5-1 节点间的关系
每个独立的DOM节点都包含指向它的相关节点的一系列指针。你需要使用这些指针来学习如何遍历DOM。所有可用的指针如图5-2所示。DOM节点指针的各个属性,分别是指向另一个DOM元素的指针(如果不存在则为null)。
图5-2 使用指针遍历DOM树
只需使用不同的指针,就可以定位页面上的任何元素或者文本块。要理解它在实际环境中是如何工作的,最好的方式就是实践一下。代码清单5-1是一个简单的HTML页。
代码清单5-1 简单HTML页面,或者说是简单的XML文档
<title>Introduction to the DOM</title>
<h1>Introduction to the DOM</h1>
<p class="test">There are a number of reasons why the
DOM is awesome, here are some:</p>
<ul>
<li id="everywhere">It can be found everywhere.</li>
<li class="test">It's easy to use.</li>
<li class="test">It can help you to find what you want, really quickly.</li>
</ul>
在这个文档中,根元素是
document.documentElement
根节点与其他DOM节点一样,同样拥有所有用于定位的指针。使用这些指针你就可以浏览整个文档,定位到任何一个你要找的元素。比如,要获取<h1>元素,你可能会使用如下方法:
//不起作用
document.documentElement.firstChild.nextSibling.firstChild
这是我们遇到的第一个难题:DOM指针不仅可以指向元素,也可以指向文本节点。在这里,代码并不会真正指向<h1>元素,而是指向了<title>元素。为什么会发生这样的事?原因在于XML存在一个有争议的地方:空格。或许你已经注意到,在
l 当试图只使用指针来遍历DOM时,精细编写的HTML标记居然会产生混乱。
l 仅使用DOM指针来定位文档可能会过于烦琐和不切实际。
l 通常,你不需要直接存取文本节点,只存取包含文本节点的元素就行了。
这让我们遇到了难题:还有其他更好的方式来寻找文档中的节点吗?当然有!在你的工具箱中创建一些辅助函数,你就可以有效提升现有的方法,并且让DOM的定位更为方便。
2.2.1 处理DOM中的空格
回到我们的HTML文档例子。之前,你试图定位到一个<h1>元素去,但由于捣乱的文本节点使得困难重重。如果只是一个比较独立的元素还好说,但是要继续找<h1>元素的下一个元素呢?你仍然会遭遇这个麻烦的空格bug,使你必须用.nextSibling.nextSibling 来跳过<h1>和<p>之间的行结束符,空格一个都不能少。代码清单5-2所示是一个用以处理空格bug的补救办法。这个特别的技术删除所有空格——当然只是DOM文档中的文本节点,让DOM的遍历更容易。这样做不仅不会对你的HTML渲染产生副作用,还会让定位DOM变得更容易。但是需要注意的是,这个函数的结果并不是持久的,它需要在HTML文档的每一次加载时都重新执行一遍。
代码清单5-2 XML文档空格bug的补救方案
function cleanWhitespace( element ) {
// 如果不提供参数,则处理整个HTML文档
element = element || document;
// 使用第一个子节点作为开始指针
var cur = element.firstChild;
// 一直到没有子节点为止
while ( cur != null ) {
// 如果节点是文本节点,并且只包含空格
if ( cur.nodeType == 3 && ! /\S/.test(cur.nodeValue) ) {
// 删除这个文本节点
element.removeChild( cur );
// 否则,它就是一个元素
} else if ( cur.nodeType == 1 ) {
// 递归整个文档
cleanWhitespace( cur );
}
cur = cur.nextSibling; // 遍历子节点
}
}
假设你要在前面例子中使用这个函数来查找位于<h1>元素后的元素。那么代码应该类似这样:
cleanWhitespace();
// 查找H1元素
document.documentElement
.firstChild // 查找Head元素
.nextSibling // 查找
.firstChild // 得到H1元素
.nextSibling // 得到相邻的段落(p)
该技术有优点也有缺点。最大的优点在于,你可以保证DOM文档的遍历在一定程度上的稳定性。但明显性能太差,想想必须遍历每个DOM元素和文本节点,目的只是为了找出包含空格的文本节点。假设你有一个包含大量内容的文档,它可能会严重降低网站的加载速度。此外,每次为文档注入新的HTML,你都需要重新扫描 DOM中的新内容,确保没有增加新的有空格填充的文本节点。
此外此函数重要的方面是节点类型的使用。节点的类型可以由检查它的nodeType属性来确定。可能会出现好几种值,但你经常会碰到的是以下3个:
l 元素(nodeType=1):匹配XML文件中的大部分元素。比如,<li>、<a>、<p>和
l 文本(nodeType=3):匹配文档内的所有文本块。当使用previousSibling和nextSibling来遍历DOM结构时,你会经常碰到元素内和元素间的文本块。
l 文档(nodeType=9):匹配文档的根元素。比如,在HTML文档内,它是
此外,你可以用常量来表明不同的DOM节点类型(但只是对非IE浏览器有用)。与其去记住1、3或9,还不如直接直观地使用document.ELEMENT_NODE、 document.TEXT_NODE或者document.DOCUMENT_NODE。因为经常清空DOM的空格会让人感到厌烦,所以你应该探索其他遍历DOM结构更有效的方法。
2.2.2 简单的DOM遍历
可以使用纯粹的DOM遍历规则(每个遍历方向都有指针)来开发一些更适合你的HTMLDOM文档遍历函数。大部分Web开发者在大多数情况下仅仅需要遍历DOM元素而非相邻的文本节点,该规则就是基于这样的事实而制定的。以下一系列的辅助函数可以帮助你,它们能够取代标准的previousSibling、nextSibling、firstChild、 lastChild和parentNode。代码清单5-3展示的函数,返回的是当前元素的前一个元素,如果前一个元素不存在则是null,类似于元素的 previousSibling属性。
代码清单5-3 查找相关元素的前一个兄弟元素的函数
function prev( elem ) {
do {
elem = elem.previousSibling;
} while ( elem && elem.nodeType != 1 );
return elem;
}
代码清单5-4展示的函数,返回的是当前元素的下一个元素,如果下一个元素不存在则是null,类似于元素的nextSibling属性。
代码清单5-4 查找相关元素的下一个兄弟元素的函数
function next( elem ) {
do {
elem = elem.nextSibling;
} while ( elem && elem.nodeType != 1 );
return elem;
}
代码清单5-5展示的函数,返回的是当前元素的第一个子元素,类似于firstChild元素属性。
代码清单5-5 查找元素第一个子元素的函数
function first( elem ) {
elem = elem.firstChild;
return elem && elem.nodeType != 1 ?
next ( elem ) : elem;
}
代码清单5-6展示的函数,返回的是当前元素的最后一个子元素,类似lastChild元素属性。
代码清单5-6 查找元素最后一个子元素的函数
function last( elem ) {
elem = elem.lastChild;
return elem && elem.nodeType != 1 ?
prev ( elem ) : elem;
}
代码清单5-7展示的函数,返回当前元素的父元素,类似parentNode元素属性。你可以一次用一个数字来操纵多个父元素,例如parent(elem,2)就等同于parent(parent(elem))。
代码清单5-7 查找元素父元素的函数
function parent( elem, num ) {
num = num || 1;
for ( var i = 0; i < num; i++ )
if ( elem != null ) elem = elem.parentNode;
return elem;
}
使用这些新函数你就可以迅速遍历DOM文档了,而且不必再为元素间的文本操心。比如,需要查找<h1>元素的下一个元素,现在可以这么做了:
// 查找<h1>元素的下一个元素
next( first( document.body ) )
在这段代码内有两件事值得注意。首先,此处有一个新的引用:document.body。所有的现代浏览器都在HTML DOM文档内通过body属性提供一个对
2.2.3 绑定到每一个HTML元素
Firefox和Opera中存在一个强大的对象原型(object prototype)叫HTMLElement,它允许你为每一个HTML DOM元素绑定函数和数据。上一节所描述的函数显得特别愚钝,但它们应该可以更清晰的。一个完美的方案是,为HTMLElement的原型直接绑定函数,由此每个独立的HTML DOM元素也直接绑定了你的函数。为了能让上一节描述的函数继续有效,你必须做出3个改变:
(1) 你需要在函数的顶部增加一行代码,使用this来引用元素,而不是从参数的变量中获取它。
(2) 你需要删除不再需要的元素参数。
(3) 你需要把函数绑定到HTMLElement原型,由此DOM中的各个HTML元素可以使用该函数。
新的next函数如代码清单5-8所示。
代码清单5-8 为所有HTML DOM元素动态绑定新的DOM遍历函数
HTMLElement.prototype.next = function() {
var elem = this;
do {
elem = elem.nextSibling;
} while ( elem && elem.nodeType != 1 );
return elem;
};
现在你可以这样来使用新的next函数(和经过同样调整后的其他函数)了:
// 一个简单的例子 —— 获取<p>元素
document.body.first().next()
这让你的代码更加清晰和易于理解。现在你可以按照自然想法的顺序来编写代码了,JavaScript总体上也更清晰了。如果你对这种编程风格感兴趣,我特别推荐尝试一下jQuery JavaScript库(http://jquery.com),它大量使用到这种技术。
注意 因为只有3个现代浏览器(Firefox、Safari和Opera)支持HTMLElement,要让它也能在IE中工作,必须给予特别的处理。有一个可以直接使用的库,由Jason Karl Davis(http://browser-land.org)编写,它为两个不支持的浏览器提供了访问HTMLElement的方法。可以从这找到该库的具体信息:http://www.browserland.org/scripts/htmlelement/.
2.2.4 标准的DOM方法
所有的现代DOM实现都包含一系列让编程更轻松的方法。结合一些自定义函数,遍历DOM可以是一种平滑的体验。那么让我们从JavaScript DOM的两个强大方法开始:
l getElementById("everywhere"):该方法只能运行在document对象下,从所有中找出ID是everywhere的元素。这是一个非常强大的函数,并且是迅速的访问一个元素的最快方法。
l getElementsByTagName("li"):该方法能运行在任何元素下,找出该元素下的所有标签名为li的后代元素,并返回一个NodeList(节点列表)。
警告 getElementById本来会按照你的预期工作:查找所有元素,揪出一个包含id的属性,且值为指定值的元素。但是,如果你加载一个远程XML 文档,并使用getElementById(或是使用除了JavaScript之外的实现DOM的语言),它默认并不会使用id属性。这是由于设计上的问题,一个XML文档如需明确指出id属性是什么,通常使用XML的定义或配置。
警告 getElementsByTagName返回一个节点NodeList.该结构跟普通的JavaScript数组非常相似,但一个重要的不同之处在于:它并不支持.push()、.pop()、.shift()等JavaScript数组的常用方法。使用getElementsByTagName时请务必记住这点,这能把你从很多混乱中解救出来。
这两个方法在所有现代的浏览器都可用,对定位到指定元素有非常大的帮助。回头看看我们前面尝试查找<h1>元素的例子,现在可以这么做了:
document.getElementsByTagName("h1")[0]
这行代码保证有效并返回文档中的第一个<h1>元素。再回头看看例子文档,假设你要获取所有<li>元素并为之加上边框。
var li = document.getElementsByTagName("li");
for ( var j = 0; j < li.length; j++ ) {
li[j].style.border = "1px solid #000";
}
最后,需要使第一个<li>元素的文本加粗,假设它已经有了一个关联它的id:
document.getElementById("everywhere").style.fontWeight = 'bold';
你可能已经注意到获取指定ID的单个元素需要冗长的代码,依靠标签名获取元素也如此。其实你可以封装一个函数来简化获取的过程:
function id(name) {
return document.getElementById(name);
}
代码清单5-9展示了一个函数,它依靠HTML DOM内的文档标签来定位元素。该函数带一个或两个参数。如果只传入一个标签名作为参数,函数将会遍历整个文档。另外你可以传入一个DOM上下文元素作为第一个参数的辅助,则函数只遍历该参数元素下的标签。
代码清单5-9 依靠HTML DOM文档标签定位元素的函数
function tag(name, elem) {
// 如果不提供上下文元素,则遍历整个文档
return (elem || document).getElementsByTagName(name);
}
让我们重新回到寻找<h1>元素后的第一个元素的问题。十分幸运的是,现在的代码可以比之前任何一次的都要精炼:
// 查找<h1>元素后的第一个元素
next( tag("h1")[0] );
以上这些函数为你在DOM文档中获取元素提供了强大而迅速的解决方法。在进一步学习修改DOM的方法之前,首先需要大致了解脚本首次执行时DOM的加载问题。
2.3 等待HTML DOM的加载
处理HTML DOM文档存在的一个难题是,JavaScript可以在DOM完全加载之前执行,这会给你的代码引发不少的潜在问题。浏览器的渲染和操作顺序大致如以下列表:
l HTML解析完毕。
l 外部脚本和样式表加载完毕。
l 脚本在文档内解析并执行。
l HTML DOM完全构造起来。
l 图片和外部内容加载。
l 网页完成加载。
在网页头部并且从外部文件加载的脚本会在HTML真正构造之前执行。如前所述,这是个至关重要的问题,因为这两处执行的脚本并不能访问还不存在的DOM。幸好,我们还有若干的补救办法。
2.3.1 等待整个页面的加载
目前,最常用的技术是完全等待整个页面加载完毕才执行DOM操作。这种技术只需利用window对象的load事件来绑定一个函数,页面加载完毕即可触发。我们将会在第6章更详细地讨论事件。代码清单5-10展示了一个页面完成加载后执行DOM相关代码的例子。
代码清单5-10 使用addEvent函数为window.onload属性绑定回调函数
// 直到页面加载完毕
// (使用的addEvent, 下一章会有阐述)
addEvent(window, "load", function() {
// 执行HTML DOM操作
next( id("everywhere") ).style.background = 'blue';
});
最简单的操作却是最慢的。在加载过程的顺序列表中,你会注意到页面的加载完毕与否完全被最后一步所掌控。这就是说,如果你页面有很多的图片、视频等,用户可能得等上一段时间JavaScript才执行。
2.3.2 等待大部分DOM的加载
第二种技术相当绕弯子,所以并不是十分推荐使用。如果你还记得,前面说过行内的脚本在DOM构造后就可立即执行。这是准真理。只有在DOM构造后,执行到该位置上脚本才真正执行。这意味着在你在页面中途嵌入的行内脚本只能访问该位置之前的DOM。所以,在页面最后元素之前嵌入脚本,你就可以在DOM中访问这个元素之前全部的元素,成为一条模拟DOM加载的伪路子。该方法典型的实现如代码清单5-11所示。
代码清单5-11 依靠在HTML DOM最后插入一个
<h1>Testing DOM Loading</h1>
<!-- 这里是大量的HTML -->
在这个例子中,你必须把行内脚本作为DOM 的最后一个元素,以便它最后一个被解析和执行。它唯一执行的是初始化函数,该函数应该包含你需要操作的DOM的相关代码。这个解决方案的最大问题在于混乱:你为HTML增添无关标记的理由仅仅是检查DOM是否已经执行。该技术通常被认为是不合理的,因为你为页面增加额外的代码的目的只是检查加载状态而已。
2.3.3 判断DOM何时加载完毕
最后一种技术可用以监听DOM加载状态,可能是最复杂的(从实现角度来看),但也是最有效的。在该技术中,你既能像绑定window加载事件那般简单,又能获得行内脚本技术那样的速度。
这项技术在不堵塞浏览器加载的情况下尽可能快地检查HTML DOM文档是否已经加载了执行所必须的属性。以下是检查HTML DOM是否可用的几个要点:
(1) document:你需要知道DOM文档是否已经加载。若能足够快地检查,运气好的话你会看到undefined。
(2) document.getElementsByTagName和documents.getElementById:频繁使用docum- ent.getElementsByTagName和document.getElementById函数检查文档,当存在这些函数则表明已完成加载。
(3) document.body:作为额外补充,检查
使用这些检查就足够判断DOM是否可用了(“足够”在此表示可能会有一定毫秒级的时间差)。这个方法几乎没有瑕疵。单独使用前述检查,脚本应该可以在现代浏览器中运行得相对良好。但是,最近 Firefox实现了缓存改进,使得window 加载事件实际上可以在脚本能检查到DOM是否可用之前触发。为了能发挥这个优势,我同时为window加载事件附加检查,以期能获得更快的执行速度。
最后,domReady函数集合了所有需要在DOM可用时就执行的函数的引用。一旦DOM被认为是可用的,就调用这些引用并按顺序一一执行。代码清单5-12展示了一个监听DOM何时加载完毕的函数。
代码清单5-12 监听DOM是否可用的函数
function domReady( f ) {
// 假如 DOM 已经加载,马上执行函数
if ( domReady.done ) return f();
// 假如我们已经增加了一个函数
if ( domReady.timer ) {
// 把它加入待执行函数清单中
domReady.ready.push( f );
} else {
// 为页面加载完毕绑定一个事件,
// 以防它最先完成。使用addEvent(该函数见下一章)。
addEvent( window, "load", isDOMReady );
// 初始化待执行函数的数组
domReady.ready = [ f ];
// 尽可能快地检查DOM是否已可用
domReady.timer = setInterval( isDOMReady, 13 );
}
}
// 检查DOM是否已可操作
function isDOMReady() {
// 如果我们能判断出DOM已可用,忽略
if ( domReady.done ) return false;
// 检查若干函数和元素是否可用
if ( document && document.getElementsByTagName &&
document.getElementById && document.body )
{
// 如果可用,我们可以停止检查
clearInterval( domReady.timer );
domReady.timer = null;
// 执行所有正等待的函数
for ( var i = 0; i < domReady.ready.length; i++ )
domReady.ready[i]();
// 记录我们在此已经完成
domReady.ready = null;
domReady.done = true;
}
}
现在该来看看这在HTML文档中是如何执行的。 domReady函数应该按addEvent函数(第6章将讨论)的方式使用,文档可操作时即绑定你需要执行的函数。代码清单5-13展示了如何使用 domReady函数来监听DOM是否可用。在这个例子中,假设已经将domReady函数写到一个名为domready.js的外部文件中。
代码清单5-13 使用domReady函数来确定何时DOM可操作和修改
<title>Testing DOM Loading</title>
<h1>Testing DOM Loading</h1>
<!--这里是大量的HTML -->
现在你已经掌握若干定位一般XML DOM文档与如何补救HTML DOM文档加载难题的方法,另一个问题也该摆上台面了:还有更好的方法来查找HTML文档中的元素吗?当然,答案是十分肯定的。
2.4 在HTML文档中查找元素
在HTML文档中与在XML文档中查找元素通常情况下还是有很大不同的。现代HTML事实上是XML的一个子集,这么说来似乎有点自相矛盾。但HTML文档包含了许多本质的不同,而这正是你可以充分利用的地方。
对JavaScript/HTML开发者来说,最重要的两个优势是利用类和CSS选择器(selector)。将这熟记于心,你就可创建一系列强大的函数,使DOM的操作更简单、更易理解。
2.4.1 通过类的值查找元素
通过类名字定位元素是一种很普遍的技术,2003年由Simon Willison(http://simon.incutio. com)普及,而原创的则是Andrew Hayward(http://www.mooncalf.me.uk)。该技术直截了当:遍历所有的元素(或者某元素的所有后代元素)直至找到指定类的元素。一种可能的实现方法如代码清单5-14所示。
代码清单5-14 找出全部有指定类值的元素的函数
function hasClass(name,type) {
var r = [];
// 定位到类值上(允许多类值)
var re = new RegExp("(^|\\s)" + name + "(\\s|$)");
// 限制类型的查找,或者遍历所有元素
var e = document.getElementsByTagName(type || "*");
for ( var j = 0; j < e.length; j++ )
// 如果元素拥有指定类,把它添加到函数的返回值中
if ( re.test(e[j]) ) r.push( e[j] );
// 返回符合的元素列表
return r;
}
现在你就可使用这个函数来在任意的或者指定类型(比如<li>或<p>)的元素中迅速查找有指定类值的元素。指定标签名会比遍历所有元素(*)的查找更快,因为需检索的目标更少。比如,在我们的HTML文档中,如果需要找出类值有test的所有元素你可以这么做:
hasClass("test")
如果只需找类值有test的<li>元素,则可以这么做:
hasClass("test","li")
当然,如果只需找出类值有test的<li>元素的第一个,则可以这么做:
hasClass("test","li")[0]
这个函数本身就已非常强大,一旦结合getElementById和getElementsByTagName,你就可以创建更强大的工具集合解决大部分棘手的DOM问题了。
2.4.2 使用CSS选择器查找元素
作为一个Web开发者,你应该已经知道选择HTML 元素的一种方案:CSS选择器。CSS选择器是用于赋予元素样式的表达式。随着CSS标准(1、2和3)的每次修订,选择器规范增加了越来越多的重要特点,由此开发者更容易精确定位到所需的元素。不幸的是,浏览器对CSS 2和CSS 3的实现慢得不可思议,所以你或许并不知道CSS中某些新奇酷的特点。如果你对这些感兴趣,建议你浏览W3C的这些主题页面:
l CSS 1选择器:http://www.w3.org/TR/REC-CSS1#basic-concepts。
l CSS 2选择器:http://www.w3.org/TR/REC-CSS2/selector.html。
l CSS 3选择器:http://www.w3.org/TR/2005/WD-css3-selectors-20051215/。
每种选择器规范可用的特点基本上都差不多,每一个后续的版本也都包含前一版的所有特点,同时,每次新版都会增加一系列的新特点。举个例子,CSS 2包含了特性(attribute)选择器和子选择器,而CSS 3则提供了额外的语言支持、性质类型选择以及逻辑非等。比如,以下所有这些都是正确的CSS选择器。
l #main<div>p:该表达式查找一个id为main的元素下所有的<div>元素,然后是这些元素下所有的<p>元素。它们都是CSS 1下的选择器。
l div.items>p:该表达式查找所有的类值为items的<div>元素,然后定位到所有的子<p>元素。这是正确的CSS 2选择器。
l div:not(.items):这则定位到所有没有值为items的类的所有<div>元素。这是正确的CSS 3选择器。
现在,你可能会觉得疑惑,实践中如果并不能使用它们来定位元素(而只能赋予样式)的话,为何会在此讨论CSS选择器。实际上很多前卫的开发者已经涉足开发能兼容CSS1甚至完全支持CSS 3的选择器的实现。使用这些库能让你顺利、方便地选择任意元素并对它们进行操作。
1.cssQuery
第一个完全支持CSS 1-3可用的公开库叫cssQuery,由Dean Edwards(http://dean.edwards.name)创立。其背后的动机很简单:你提供CSS选择器,cssQuery帮你找出所有匹配的元素。此外,cssQuery可以分解成多个子库,有一个是每个CSS选择器“管理员”,它甚至能执行CSS 3的语法,如果有必要的话。这个独特的库非常复杂但能运行在所有现代浏览器上(Dean是一位跨浏览器支持的忠实拥趸)。要应用整个库,你需要提供选择器,也可选择加入上下文元素以便加快搜索。以下是例子:
// 查找所有<div>元素的子<p>元素
cssQuery("div > p");
// 查找所有的<div>, <p>和<form>元素
cssQuery("div,p,form");
// 查找所有<p>元素和<div>元素,然后查找在这些元素内的a元素
var p = cssQuery("p,div");
cssQuery("a",p);
执行cssQuery函数会返回匹配的元素列表。由此你就可以像使用getElementsByTagName一样对元素进行操作。比如,为所有指向Google的链接增加边框,可按如下方法做:
// 为所有指向Google的链接增加边框
var g = cssQuery("a[href^='google.com']");
for ( var i = 0; i < g.length; i++ ) {
g[i].style.border = "1px dashed red";
}
关于cssQuery的更多信息可以在Dean Edwards的网站上获取,那里同时也提供完整的源码下载:http://dean.edwards.name/my/cssQuery/.
提示 Dean Edwards是一位JavaScript魔术师,他的代码令人惊讶。强烈建议你稍加浏览他的cssQuery库,看看他的JavaScript扩展性写得多么好。
2.jQuery
尽管jQuery是JavaScript库世界的新成员,但它提供了一些新颖而引人注目的JavaScript编程方式。我最初只想把jQuery写成一个“简便”的CSS选择器库,类似于 cssQuery,直到Dean Edwards发布了卓越的cssQuery库,迫使我开辟了另一个不同的方向。这个库提供了完整的CSS 1-3的支持,同时也有基本的XPath支持。在此之上,它还提供了进一步定位和操作DOM的能力。跟cssQuery一样,jQuery完全支持现代浏览器。以下是使用混合CSS和XPath的jQuery自定义方式来选择元素的例子:
// 查找所有类值为'links', 同时内有p元素的<div>元素
$("div.links[p]")
// 查找所有<p>, <div>元素的所有子孙元素
$("p,div").find("*")
// 查找指向Google的所有偶数链接
$("a[@href^='google.com']:even")
现在,如需进一步使用jQuery元素选择返回的结果,你有两种方式。第一,你可以运行$("expression").get()来获取匹配元素的列表——这跟cssQuery完全一致。第二,你可以使用jQuery内置的特殊函数来操作CSS和DOM。所以,回到使用cssQuery为指向Google的链接加边框的例子上,你现在就可以这么做:
// 为所有指向Google的链接增加边框
$("a[@href^=google.com]").css("border","1px dashed red");
你可以从jQuery项目网站上找到大量的例子、演示和文档等,此外,还有可定制的下载:http://jquery.com/。
注意 应该指出的是,cssQuery或jQuery实际上并不只是能定位HTML文档而已,它们可以在任何XML文档上使用。至于纯粹的XML形式定位,请继续阅读下面的XPath部分。
2.4.3 XPath
XPath表达式是一种不可思议的定位XML文档的强大的方式。自问世几年来,几乎可以肯定DOM实现的背后必有XPath。尽管相对冗长,但XPath表达式比起CSS选择器来,能做的事情更多也更强大。表5-1并行比较了一些CSS选择器和XPath表达式的某些不同。
表5-1 CSS 3选择器与XPath表达式的比较
目 标
CSS 3
XPath
所有元素
*
//*
所有<p>元素
p
//p
所有子元素
p> *
//p/*
由ID获取元素
#foo
//*[@id='foo']
由类获取元素
.foo
//*[contains(@class, 'foo')]
由特性(attribute)获取元素
*[title]
//*[@title]
<p>的第一个子元素
p >*:first-child
//p/*[0]
所有拥有子元素的<p>
不支持
//p[a]
下一个元素
p + *
//p/下一兄弟元素::*[0]
如果前面这些表达式激发了你的兴趣,推荐你浏览XPath的两个规范(尽管现代浏览器通常只完全支持XPath 1.0)以初步了解它们是如何工作的:
l XPath 1.0:http://www.w3.org/TR/xpath/。
l XPath 2.0:http://www.w3.org/TR/xpath20/。
如果需要深入这个主题,我推荐你阅读O’Reilly出版的由Elliotte Harold和Scott Means所著的XML in a Nutshell(2004),或者Apress出版的由Jeni Tennison所著的Beginning XSLT 2.0: From Novice to Professional(2005)。此外,还有一些精彩的教程帮助你学习使用XPath:
l W3Schools的XPath教程:http://w3schools.com/xpath/。
l ZVON XPath 教程:http://zvon.org/xxl/XPathTutorial/General/examples.html。
目前,浏览器对XPath的支持各不相同:IE和 Mozilla都比较完整(虽然不一样)地支持XPath实现,而Safari和Opera的版本还在开发中。作为补救,有几种完全使用 JavaScript写的XPath实现方法。虽然它们通常会比较慢(跟基于浏览器的实现相比),但可以确保所有现代浏览器都能稳定运行:
l XML for Script:http://xmljs.sf.net/。
l Google AJAXSLT:http://goog-ajaxslt.sf.net/。
此外,一个名为Sarissa的项目(http://sarissa.sf.net/)打算为各种浏览建立一个通用的实现包装。它可以让你一次写成XML访问代码,同时仍能从支持的浏览器中获得更快的运行速度。这项技术的最大问题在于,Opera和Safari浏览器对XPath支持仍不足,走不出XPath先前实现的困境。
使用浏览器内置的XPath与使用得到广泛支持的纯粹的JavaScript解决方案比起来,通常被认为是试验性质的技术。尽管如此,XPath的使用和普及渐渐成长起来,可把它当作CSS选择器强有力的竞争对手。
现在你已经掌握了定位任意DOM元素或任意DOM元素集合的必要知识和工具,那么我们现在应该利用这些知识了:从特性的操作到增加和删除DOM元素。
2.5 获取元素的内容
所有的DOM元素无外乎包含以下三者之一:文本、元素、文本与元素的混合。一般来说,常见的是第一种和最后一种。在这一节中你将了解现有的获取元素内容的常用方法。
2.5.1 获取元素内的文本
获取元素内的文本可能会令DOM新手最容易迷惑,同时它也配合了HTML DOM文档和XML DOM文档。在图5-3所示的DOM结构例子中,有一个<p>主元素,它包含一个<strong>元素和一段文本块。<strong>元素本身同时包含文本块。
让我们来看看如何获取这些元素中的文本。<strong>元素很简单,因为它除了文本节点没别的干扰信息。
应该注意的是,所有非基于Mozilla的浏览器中都有一个叫作innerText的属性,可用它来获取元素内的文本。这十分方便。不幸的是,因为它在一个比较流行的浏览器上并不可用,同时在XML DOM文档中也不行,你还是需要开发一个可行的替代方案。
获取元素内文本内容的一个窍门是,需要记住元素并不是直接包含文本的,而包含在一个子文本节点中,这可能有点让人不习惯。假设变量strongElem包换一个<strong>元素的引用,让我们来看看代码清单5-15是如何使用DOM从元素内抽出文本的。
代码清单5-15 获取<strong>元素的文本内容
// 非Mozilla浏览器:
strongElem.innerTex
// 其他的浏览器:
strongElem.firstChild.nodeValue
了解如何从单个元素中获取文本内容后,你需要继续学习如何从<p>元素中获取经过组合的文本内容。为此,你或许需要开发一个通用的函数来获取任意元素的文本内容,而不用考虑元素究竟嵌套了几层,如代码清单5-16所示。调用text()将返回元素及其所有后代元素包含的文本内容。
代码清单5-16 一个获取元素文本内容的通用函数
function text(e) {
var t = "";
// 如果传入的是元素,则继续遍历其子元素,
// 否则假定它是一个数组
e = e.childNodes || e;
// 遍历所有字节点
for ( var j = 0; j < e.length; j++ ) {
// 如果不是元素,追加其文本值
// 否则,递归遍历所有元素的子节点
t += e[j].nodeType != 1 ?
e[j].nodeValue : text(e[j].childNodes);
}
// 返回匹配的文本
return t;
}
使用这个能够获取任何元素文本内容的函数,在上面这个例子中,你就可以从<p>元素中获取它的文本内容了,代码大致如下:
// 获取<p>元素的文本内容
text( pElem );
这个函数还有一个显著的好处,那就是它可以确保在HTML和XML DOM文档中都能工作,那就是说你现在拥有了一个能获取任何元素文本内容的兼容方法。
2.5.2 获取元素内的HTML
与获取元素内的文本比起来,获取元素内的HTML是一项更容易上手的DOM任务。令人高兴的是,由于一个由IE小组开发的特色方法,所有的现代浏览器现在都实现了HTML DOM元素的一个额外属性:innerHTML。使用这个属性你就可以从一个元素中提取所有HTML和文本了。此外,使用innerHTML十分快——通常会比递归查找元素内的所有文本内容快得多。但是并非事事称心如意。它取决于浏览器如何去实现innerHTML属性,而且因为并没有标准,每种浏览器都可以返回它自以为是的内容。例如,以下是一些使用innerHTML属性的怪异bug:
l 基于Mozilla的浏览器在innerHTML声明中并不会返回<style>元素。
l IE返回的元素字符都是大写的,如果你想保持一致性可能会感到失望。
l innerHTML作为一个只能用在HTML DOM文档的元素中的属性,若在XML DOM文档中使用的话只会返回null值。
使用innerHTML属性是很直观的,存取该属性会返回一个包含该元素的HTML内容的字符串。如果元素仅包含文本而不包含任何子元素,则返回的字符串只包含文本。我们通过调试图5-2中两个元素来看它是如何工作的:
// 获取<strong>元素的innerHTML
// 应返回"Hello"
strongElem.innerHTML
// 获取<p>元素的innerHTML
// 应返回"<strong>Hello</strong> how are you doing?"
pElem.innerHTML
如果你确定元素只包含文本,这个方法也可用来取代前面那个获取元素文本的复杂方法,并且十分简便。另一方面,能获取元素内的HTML内容,你就可以利用就地编辑的特点来构建一些动态的时髦应用。在第10章我们会讨论这个主题。
2.6 操作元素特性
几乎跟获取元素内容一样,获取和设置元素特性(attribute)的值也是最频繁的操作之一。通常,元素自带的特性列表会随着元素自身的XML表示信息预先加载,并存储成关联数组备用,例如以下这个网页的HTML片段:
<form name="myForm" action="/test.cgi" method="POST">
...
</form>
一旦加载到DOM中,表示HTML表单元素的变量formElem就会有一个关联数组,它是名/值特性对。其结果大致如下:
formElem.attributes = {
name: "myForm",
action: "/test.cgi",
method: "POST"
};
检查元素特性是否存在对于使用特性数组来说是最简单不过的,但还是存在一个问题:不知何故Safari不支持。最为严重的是,IE也不支持可能是最有用的hasAttribute函数。那么如何才能检查某个特性是否存在呢?一个可行的办法是使用getAttribute函数(我将会在下一节讲述)并且检查返回值是否为null,如代码清单5-17所示。
代码清单5-17 检查元素是否有用一个指定的特性
function hasAttribute( elem, name ) {
return elem.getAttribute(name) != null;
}
有了这个函数,并了解特性如何使用,那么你就可以获取和设置特性的值了。
获取和设置特性的值
根据你所使用的DOM文档的类型,从元素中获取特性的信息有两种方法。如果你希望保险并且总是兼容通用XML DOM的方式,那么可用getAttribute和setAttribute。可以使用如下方法:
// 获取特性
id("everywhere").getAttribute("id")
// 设置特性的值
tag("input")[0].setAttribute("value","Your Name");
除了这对标准的 getAttribute/setAttribute外,HTML DOM文档还有作为快速特性获取器(getter)/设置器(setter)的额外属性集合。它们通常在现代浏览器的DOM实现中都可用(但只能给 HTML DOM文档打包票),使用它们十分有助于编写精炼的代码。以下的代码向你展示了如何使用DOM属性(property)同时取得和设置DOM特性:
// 快捷获取特性
tag("input")[0].value
// 快捷设置特性
tag("div")[0].id = "main";
你应该注意到,特性存在某些怪异的情形。最常碰到问题的情形之一是存取类名称特性。为了能在所有的浏览器中都生效,你必须使用elem.className来存取className特性,而不是使用看起来更合适的getAttribute("class")。这种情形也发生在for特性上,而它被另外起名为htmlFor。此外,这也会在一些CSS特性上发生:cssFloat和cssText。导致这些特别的命名约定是因为class、for、float和text等都是JavaScript的保留字。
为了补救这些怪异的情形并且简化获取、设置正确特性的处理流程,你应该使用一个辅助函数来处理这些特殊情形。代码清单5-18展示了一个获取和设置元素特性值的函数。使用两个参数来调用这个函数时,比如 attr(element,id),返回指定元素特性的值;而使用3个参数来调用的话,比如attr(element,class,test),则是设置特性的值并返回它的新值。
代码清单5-18 获取和设置元素特性的值
function attr(elem, name, value) {
// 确保提供的 name 是正确的
if ( !name || name.constructor != String ) return '';
// 检查name是否处在怪异命名的情形中
name = { 'for': 'htmlFor', 'class': 'className' }[name] || name;
// 如果用户传入了value参数的话,那么
if ( typeof value != 'undefined' ) {
// 首先使用快捷方式
elem[name] = value;
// 可以的话,使用setAttribute
if ( elem.setAttribute )
elem.setAttribute(name,value);
}
// 返回特性的值
return elem[name] || elem.getAttribute(name) || '';
}
不用关心具体实现,使用标准的方法来同时存取和改变特性是一个强大的工具。代码清单5-19展示了一些例子,从中你可以学习到如何在多种场合中使用attr函数来简化处理特性的流程。
代码清单5-19 在DOM元素中使用attr函数设置和获取特性的值
// 设置<h1>元素的class
attr( tag("h1")[0], "class", "header" );
// 设置各个<input>元素的值
var input = tag("input");
for ( var i = 0; i < input.length; i++ ) {
attr( input[i], "value", "" );
}
// 为name的值是'invalid'的<input>元素增加边框
var input = tag("input");
for ( var i = 0; i < input.length; i++ ) {
if ( attr( input[i], "name" ) == 'invalid' ) {
input[i].style.border = "2px solid red";
}
}
到目前为止,我们已经讨论了DOM中常用的特性(比如id、class、name等)的获取和设置。但是,还有一个派得上用场的技术,那就是设置和获取非传统的特性。比如,你可以增添一个新特性(它只对存取DOM版本的元素可见)然后再次获取,完全不用修改文档的实际属性。举个例子,假设你有一个术语的自定义列表,并需要在点击术语的时候展开它的描述,HTML的结构大致如代码清单5-20所示。
代码清单5-20 自定义列表的HTML,其中把描述事先隐藏起来
<title>Expandable Definition List</title>
<style>dd { display: none; }</style>
<h1>Expandable Definition List</h1>
<dl>
<dt>Cats</dt>
<dd>A furry, friendly, creature.</dd>
<dt>Dog</dt>
<dd>Like to play and run around.</dd>
<dt>Mice</dt>
<dd>Cats like to eat them.</dd>
</dl>
我们将会在第6章详细讲解事件的细节,现在先尽量保持这些事件代码的简单。以下例子是一个能让你点击术语显示(或隐藏)描述的高效脚本。该脚本应在页面的头部或者从一个外部文件中引入。代码清单5-21展示了构建一个可扩展自定义列表的必要代码。
代码清单5-21 允许动态切换的自定义列表
// 直至DOM可用
domReady(function(){
// 遍历所有的术语
var dt = tag("dt");
for ( var i = 0; i < dt.length; i++ ) {
// 等待用户点击术语
addEvent( dt[i], "click", function() {
// 检查描述已经open与否
var open = attr( this, "open" );
// 切换描述的display
next( this ).style.display = open ? 'none' : 'block';
// 记录描述是否open
attr( this, "open", open ? '' : 'yes' );
});
}
});
现在已经知道如何遍历DOM与如何检查及修改特性,接下来你需要学习如何创建新的DOM元素,插入到所需的地方,并删除不再需要的元素。
2.7 修改DOM
了解如何修改DOM后,你就可以干任何事了,从即时创建自定义的XML文档到建立接受用户键入的动态表单,几乎所有可能的操作。修改DOM有3个步骤:首先要知道怎么创建一个新元素,然后要知道如何把它插入DOM中,最后要学习如何删除它。
2.7.1 使用DOM创建节点
修改DOM的主要方法是使用createElement函数,它可以让你即刻创建新元素。但是,已经创建的新元素并不会马上插入到DOM中(刚开始使用DOM的人常会对此感到迷惑)。所以,我先把重点放在创建DOM元素上。
createElement方法带有元素的标记名称,并返回该元素的实际DOM引用,这里没有特性和样式。如果你要开发使用XSLT驱动的XHTML页面(或者使用正确的MIME侍服的XHTML页面)的应用程序,必须记住,你使用的是XML文档,所以创建的元素必须使用正确的XML命名空间来关联它们。为无缝地解决问题,你可以使用一个简单的函数,用它来测试你正使用的HTML DOM文档是否支持使用命名空间(XHTML DOM文档的一个特点)来创建新的元素。在这种情况下,你必须使用正确的XHTML命名空间来创建新的DOM元素,如代码清单5-22所示。
代码清单5-22 创建新DOM元素的通用函数
function create( elem ) {
return document.createElementNS ?
document.createElementNS( 'http://www.w3.org/1999/xhtml', elem ) :
document.createElement( elem );
}
例如,使用这个函数你就可以创建一个简单的<div>元素,并附上一些额外的信息:
var div = create("div");
div.className = "items";
div.id = "all";
此外需要注意的是,一个创建新文本节点的DOM方法叫做createTextNode。它需要传入一个参数,即你需要插入到节点中的文本,并返回已创建的文本节点。
使用新建立的DOM元素和文本节点,就可以把它们插入到DOM文档所需的位置中去了。
2.7.2 插入到DOM中
即使是经验丰富的DOM老手,有时也难免会感到插入元素到DOM中非常麻烦。在你的JavaScript工具库中准备以下两个函数可以让你把事情做好。
第一个函数是insertBefore,可以在另一个子元素前插入一个元素。使用该函数的方法类似如下例子:
parentOfBeforeNode.insertBefore( nodeToInsert, beforeNode );
我使用了一个助记诀窍辅助记忆它的参数顺序:“insert第一个元素,before第二个。”没错吧,你只需片刻就记住了它。
有了一个能在其他节点前插入节点(可以是元素,也可以是文本节点),你是否该考虑考虑:“如何插入一个父节点中最后一个子节点?”一个名为appendChild的函数可以做到这个。 appendChild调用一个元素参数,追加指定的节点到子节点列表中的最后一个。这个函数的用法大致如下:
parentElem.appendChild( nodeToInsert );
为避免死记硬背insertBefore和 appendChild的参数顺序,你可以使用我写的两个辅助函数解决问题,如代码清单5-23和代码清单5-24所示,参数以相关的元素/节点然后是需插入的元素/节点的顺序调用。此外,before函数还有一个传入父节点的可选项,它可以为你节约一些代码。最后,两个函数都允许你传入需要插入/追加的字符串并自动地帮你转化为文本节点。建议你提供父元素作为引用(防止elem变成null)。
代码清单5-23 在另一个元素之前插入元素的函数
function before( parent, before, elem ) {
// 检查parent是否传入
if ( elem == null ) {
elem = before;
before = parent;
parent = before.parentNode;
}
parent.insertBefore( checkElem( elem ), before );
}
代码清单5-24 为另一个元素追加一个子元素的函数
function append( parent, elem ) {
parent.appendChild( checkElem( elem ) );
}
代码清单5-25的辅助函数允许你插入元素和文本(自动转化为正确的文本节点)。
代码清单5-25 before和append的辅助函数
function checkElem( elem ) {
// 如果只提供字符串,则把它转化成文本节点
return elem && elem.constructor == String ? document.createTextNode( elem ) : elem;
}
现在,使用before和append函数,通过创建新的DOM元素,你就可以在DOM中增添更多的信息呈现给用户了,如代码清单5-26所示。
代码清单5-26 使用append和before函数
// 创建一个新的<li>元素
var li = create("li");
attr( li, "class", "new" );
// 创建文本内容并添加到<li>中
append( li, "Thanks for visiting!" );
// 把<li>插入到有序列表的顶端
before( first( tag("ol")[0] ), li );
// 运行这些语句会转化空<ol>
<ol></ol>
// 为以下:
<ol>
<li class='new'>Thanks for visiting!</li>
</ol>
一将这些信息“插入”到DOM(包括使用insertBefore和appendChild),它马上就会渲染并呈现在用户前。因此,你可用之作实时的反馈。这对于需要用户输入的交互应用尤其有用。
现在你看到的只是基于DOM的方法来创建和插入节点,进一步了解其他向DOM注入内容的方法将更有用。
2.7.3 注入HTML到DOM
比创建普通DOM元素并插入到DOM中更为流行的技术是,直接向文档注入HTML。这个最为简便的实现方法使用了前面讨论的innerHTML。除了从元素获取HTML之外,它同时也是设置元素内的HTML的一条途径。作为一个展示其简单性的例子,假设有一个空的<ol>元素并需要往里加入<li>,那么的代码大致如下:
// 给ol 元素加入部分li
tag("ol")[0].innerHTML = "<li>Cats.</li><li>Dogs.</li><li>Mice.</li>";
难道这不比繁复地创建大量的DOM元素和相应的文本节点要简单得多?更好的是,它还比使用DOM方法要快得多(根据http://quirksmode.org的说法)。但世事无完美,使用innerHTML注入方法也伴随着一些棘手的问题:
l 如前所述,XML DOM文档中并不存在innerHTML方法,就是说你得继续使用通用的DOM 创建方法。
l 使用客户端XSLT创建的XHTML文档也不存在innerHTML方法,因为它们也是纯粹的XML文档。
l innerHTML完全删除了元素内容的任何节点,意味着没有像DOM方法一样追加或者插入的简便方法。
最后一条尤为棘手,因为在另一个元素之前插入元素或者追加到子元素列表末尾特别有用。但是,编织一些DOM魔法,去改造append和before方法,除了常规的DOM元素,还可以让它跟常规的HTML 字符串和平共处。改造过程分两步走。第一步建立一个checkElem函数,它可以处理HTML字符串、DOM元素以及DOM元素数组,如代码清单 5-27所示。
代码清单5-27 转化一个DOM节点/HTML字符串混合型参数数组为纯粹的DOM节点数组
function checkElem(a) {
var r = [];
// 如果参数不是数组, 则强行转换
if ( a.constructor != Array ) a = [ a ];
for ( var i = 0; i < a.length; i++ ) {
// 如果是字符串
if ( a[i].constructor == String ) {
// 用一个临时元素来存放HTML
var div = document.createElement("div");
// 注入HTML, 转换成DOM结构
div.innerHTML = a[i];
// 提取DOM结构到临时 div 中
for ( var j = 0; j < div.childNodes.length; j++ )
r[r.length] = div.childNodes[j];
} else if ( a[i].length ) { // If it's an array
// 假定是DOM节点数组
for ( var j = 0; j < a[i].length; j++ )
r[r.length] = a[i][j];
} else { // 否则,假定是DOM节点
r[r.length] = a[i];
}
}
return r;
}
第二步,你需要修改这两个插入函数以适应新的checkElem,接受数组元素,如代码清单5-28所示。
代码清单5-28 插入和追加内容到DOM的改进函数
function before( parent, before, elem ) {
// 检查是否提供 parent 节点参数
if ( elem == null ) {
elem = before;
before = parent;
parent = before.parentNode;
}
// 获取元素的新数组
var elems = checkElem( elem );
// 向后遍历数组,
// 因为我们向前插入元素
for ( var i = elems.length - 1; i >= 0; i-- ) {
parent.insertBefore( elems[i], before );
}
}
function append( parent, elem ) {
// 获取元素数组
var elems = checkElem( elem );
// 把它们所有都追加到元素中
for ( var i = 0; i <= elems.length; i++ ) {
parent.appendChild( elems[i] );
}
}
现在,使用这些新函数为有序列表追加<li>就非常简单了:
append( tag("ol")[0], "<li>Mouse trap.</li>" );
// 运行该简单的一行就能追加HTML到这个<ol>中
<ol>
<li>Cats.</li>
<li>Dogs.</li>
<li>Mice.</li>
</ol>
// 把它变成了这样:
<ol>
<li>Cats.</li>
<li>Dogs.</li>
<li>Mice.</li>
<li>Mouse trap.</li>
</ol>
// 而用before()运行一个类似的语句
before( last( tag("ol")[0] ), "<li>Zebra.</li>" );
// 则会变成这样:
<ol>
<li>Cats.</li>
<li>Dogs.</li>
<li>Zebra.</li>
<li>Mice.</li>
</ol>
这将能帮助你开发简洁和清晰的代码。但是,如果从DOM中移动元素和删除节点要怎么办呢?当然,肯定也会有其他方法处理这些问题。
2.7.4 删除DOM节点
删除DOM节点的操作几乎与创建和插入一样频繁。根据用户的需求允许创建无限量的项目,那么允许用户能够删除它们之中再也不需要处理的部分也就变得很重要了。删除节点的能力可封装成一个函数:removeChild。它跟appendChild用法一致效果相反。该函数的用法大致如下:
NodeParent.removeChild( NodeToRemove );
记住这点,你就可以创建两个独立的函数来删除节点了,如代码清单5-29所示。
代码清单5-29 删除DOM节点的函数
// 删除一个独立的DOM节点
function remove( elem ) {
if ( elem ) elem.parentNode.removeChild( elem );
}
代码清单5-30展示了从一个元素中删除所有子节点的函数,仅需要DOM元素的引用作为参数。
代码清单5-30 从一个元素中删除所有子节点的函数
// 在DOM中删除一个元素的所有子节点
function empty( elem ) {
while ( elem.firstChild )
remove( elem.firstChild );
}
举个例子,要删除上一节例子中添加的<li>,同时假设你已经给用户充足的时间来浏览<li>了,在没有提示的情况下被删除。可以使用以下的JavaScirpt代码来完成这个操作:
// 删除<ol>的最后一个<li>
remove( last( tag("ol")[0] ) )
// 将会把
<ol>
<li>Learn Javascript.</li>
<li>???</li>
<li>Profit!</li>
</ol>
// 转换成:
<ol>
<li>Learn Javascript.</li>
<li>???</li>
</ol>
// 如果要求执行empty()函数而不是remove()
empty( last( tag("ol")[0] ) )
// 它将会简单地清空<ol>, 只留下:
<ol></ol>
学习完删除DOM节点,你已经清楚了我们的文档对象模型是如何运作的,并学会如何充分运用它。
2.8 小结
在这一章里讨论了与文档对象模型相关的大量东西。不幸的是,其中有些主题比其他的要复杂得多(比如等待DOM加载),而且在一段时间里都一直会这么复杂。不过仅使用目前所学你也能搭建出任何动态的Web应用程序了。
如果想看看一些DOM脚本编程的实际运用,在附录A中包含了大量补充代码。此外,还可以从本书的网站http://jspro.org或Apress网站 http://www.apress.com的源代码/下载部分找到更多DOM脚本编程例子。接下来,你要把注意力转向分离式DOM脚本编程的下一个组成部分——事件。