文檔對象模型(Document Object Model,DOM)是用于操縱 XML 和 HTML 數(shù)據(jù)的最常用工具之一,然而它的潛力卻很少被充分挖掘出來。通過利用 DOM 的優(yōu)勢,并使它更加易用,您將獲得一款應(yīng)用于 XML 應(yīng)用程序(包括動態(tài) Web 應(yīng)用程序)的強大工具。
本期文章介紹了一位客串的專欄作家,同時也是我的朋友和同事 Dethe Elza。Dethe 在利用 XML 進行 Web 應(yīng)用程序開發(fā)方面經(jīng)驗豐富,在此,我要感謝他對我在介紹使用 DOM 和 ECMAScript 進行 XML 編程這一方面的幫助。請密切關(guān)注本專欄,以了解 Dethe 的更多專欄文章。 —— David Mertz
DOM 是處理 XML 和 HTML 的標(biāo)準(zhǔn) API 之一。由于它占用內(nèi)存大、速度慢,并且冗長,所以經(jīng)常受到人們的指責(zé)。盡管如此,對于很多應(yīng)用程序來說,它仍然是最佳選擇,而且比 XML 的另一個主要 API —— SAX 無疑要簡單得多。DOM 正逐漸出現(xiàn)在一些工具中,比如 Web 瀏覽器、SVG 瀏覽器、OpenOffice,等等。
DOM 很好,因為它是一種標(biāo)準(zhǔn),并且被廣泛地實現(xiàn),同時也內(nèi)置到其他標(biāo)準(zhǔn)中。作為標(biāo)準(zhǔn),它對數(shù)據(jù)的處理與編程語言無關(guān)(這可能是優(yōu)點,也可能是缺點,但至少使我們處理數(shù)據(jù)的方式變得一致)。DOM 現(xiàn)在不僅內(nèi)置于 Web 瀏覽器,而且也成為許多基于 XML 的規(guī)范的一部分。既然它已經(jīng)成為您的工具的一部分,并且或許您偶爾還會使用它,我想現(xiàn)在應(yīng)該充分利用它給我們帶來的功能了。
在使用 DOM 一段時間后,您會看到形成了一些模式 —— 您想要反復(fù)做的事情。快捷方式可以幫助您處理冗長的 DOM,并創(chuàng)建自解釋的、優(yōu)雅的代碼。這里收集了一些我經(jīng)常使用的技巧和訣竅,還有一些 JavaScript 示例。
第一個訣竅就是“沒有訣竅”。DOM 有兩種方法將孩子節(jié)點添加到容器節(jié)點(常常是一個 Element
,也可能是一個 Document
或 DocumentFragment
):appendChild(node)
和 insertBefore(node, referenceNode)
。看起來似乎缺少了什么。假如我想在一個參考節(jié)點后面插入或者由前新增(prepend)一個子節(jié)點(使新節(jié)點位于列表中的第一位),我該怎么做呢?很多年以來,我的解決方法是編寫下列函數(shù):
清單 1. 插入和由前新增的錯誤方法
|
實際上,像清單 1 一樣,insertBefore()
函數(shù)已經(jīng)被定義為,在參考節(jié)點為空時返回到 appendChild()
。因此,您可以不使用上面的方法,而使用 清單 2 中的方法,或者跳過它們僅使用內(nèi)置函數(shù):
清單 2. 插入和由前新增的正確方法
|
如果您剛剛接觸 DOM 編程,有必要指出的是,雖然您可以使多個指針指向一個節(jié)點,但該節(jié)點只能存在于 DOM 樹中的一個位置。因此,如果您想將它插入到樹中,沒必要先將它從樹中移除,因為它會自動被移除。當(dāng)重新將節(jié)點排序時,這種機制很方便,僅需將節(jié)點插入到新位置即可。
根據(jù)這種機制,若想交換兩個相鄰節(jié)點(稱為 node1
和 node2
)的位置,可以使用下列方案之一:
|
或
|
![]() ![]() |
![]()
|
Web 頁面中大量應(yīng)用了 DOM。若訪問 bookmarklets 站點(參閱 參考資料),您會發(fā)現(xiàn)很多有創(chuàng)意的簡短腳本,它們可以重新編排頁面,提取鏈接,隱藏圖片或 Flash 廣告,等等。
但是,因為 Internet Explorer 沒有定義 Node
接口常量(可以用于識別節(jié)點類型),所以您必須確保在遺漏接口常量時,首先為 Web 在 DOM 腳本中定義接口常量。
清單 3. 確保節(jié)點被定義
|
清單 4 展示如何提取包含在節(jié)點中的所有文本節(jié)點:
清單 4. 內(nèi)部文本
|
![]() ![]() |
![]()
|
人們常常抱怨 DOM 太過冗長,并且簡單的功能也需要編寫大量代碼。例如,如果您想創(chuàng)建一個包含文本并響應(yīng)點擊按鈕的 <div>
元素,代碼可能類似于:
清單 5. 創(chuàng)建 <div> 的“漫長之路”
|
若頻繁按照這種方式創(chuàng)建節(jié)點,鍵入所有這些代碼會使您很快疲憊不堪。必須有更好的解決方案 —— 確實有這樣的解決方案!下面這個實用工具可以幫助您創(chuàng)建元素、設(shè)置元素屬性和風(fēng)格,并添加文本子節(jié)點。除了 name
參數(shù),其他參數(shù)都是可選的。
清單 6. 函數(shù) elem() 快捷方式
|
使用該快捷方式,您能夠以更加簡潔的方法創(chuàng)建 清單 5 中的 <div>
元素。注意,attrs
和 style
參數(shù)是使用 JavaScript 文本對象而給出的。
清單 7. 創(chuàng)建 <div> 的簡便方法
|
在您想要快速創(chuàng)建大量復(fù)雜的 DHTML 對象時,這種實用工具可以節(jié)省您大量的時間。模式在這里就是指,如果您有一種需要頻繁創(chuàng)建的特定的 DOM 結(jié)構(gòu),則使用實用工具來創(chuàng)建它們。這不但減少了您編寫的代碼量,而且也減少了重復(fù)的剪切、粘貼代碼(錯誤的罪魁禍?zhǔn)祝⑶以陂喿x代碼時思路更加清晰。
![]() ![]() |
![]()
|
DOM 通常很難告訴您,按照文檔的順序,下一個節(jié)點是什么。下面有一些實用工具,可以幫助您在節(jié)點間前后移動:
清單 8. nextNode 和 prevNode
|
![]() ![]() |
![]()
|
有時候,您可能想要遍歷 DOM,在每個節(jié)點調(diào)用函數(shù)或從每個節(jié)點返回一個值。實際上,由于這些想法非常具有普遍性,所以 DOM Level 2 已經(jīng)包含了一個稱為 DOM Traversal and Range 的擴展(為迭代 DOM 所有節(jié)點定義了對象和 API),它用來為 DOM 中的所有節(jié)點應(yīng)用函數(shù)和在 DOM 中選擇一個范圍。因為這些函數(shù)沒有在 Internet Explorer 中定義(至少目前是這樣),所以您可以使用 nextNode()
來做一些類似的事情。
在這里,我們的想法是創(chuàng)建一些簡單、普通的工具,然后以不同的方式組裝它們來達到預(yù)期的效果。如果您很熟悉函數(shù)式編程,這看起來會很親切。Beyond JS 庫(參閱 參考資料)將此理念發(fā)揚光大。
清單 9. 函數(shù)式 DOM 實用工具
|
清單 9 包含了 4 個基本工具。listNodes()
和 listNodesReversed()
函數(shù)可以擴展到一個可選的長度,這與 Array
的 slice()
方法效果類似,我把這個作為留給您的練習(xí)。另一個需要注意的是,map()
和 filter()
函數(shù)是完全通用的,用于處理任何 列表(不只是節(jié)點列表)。現(xiàn)在,我向您展示它們的幾種組合方式。
清單 10. 使用函數(shù)式實用工具
|
您可以使用這些實用工具來提取 ID、修改樣式、找到某種節(jié)點并移除,等等。一旦 DOM Traversal and Range API 被廣泛實現(xiàn),您無需首先構(gòu)建列表,就可以用它們修改 DOM 樹。它們不但功能強大,并且工作方式也與我在上面所強調(diào)的方式類似。
注意,核心 DOM API 并不能使您將 XML 數(shù)據(jù)解析到 DOM,或者將 DOM 序列化為 XML。這些功能都定義在 DOM Level 3 的擴展部分“Load and Save”,但它們還沒有被完全實現(xiàn),因此現(xiàn)在不要考慮這些。每個平臺(瀏覽器或其他專業(yè) DOM 應(yīng)用程序)有自己在 DOM 和 XML 間轉(zhuǎn)換的方法,但跨平臺轉(zhuǎn)換不在本文討論范圍之內(nèi)。
DOM 并不是十分安全的工具 —— 特別是使用 DOM API 創(chuàng)建不能作為 XML 序列化的樹時。絕對不要在同一個程序中混合使用 DOM1 非名稱空間 API 和 DOM2 名稱空間感知的 API(例如,createElement
和 createElementNS
)。如果您使用名稱空間,請盡量在根元素位置聲明所有名稱空間,并且不要覆蓋名稱空間前綴,否則情況會非常混亂。一般來說,只要按照慣例,就不會觸發(fā)使您陷入麻煩的臨界情況。
如果您一直使用 Internet Explorer 的 innerText
和 innerHTML
進行解析,那么您可以試試使用 elem()
函數(shù)。通過構(gòu)建類似的一些實用工具,您會得到更多便利,并且繼承了跨平臺代碼的優(yōu)越性。將這兩種方法混合使用是非常糟糕的。
某些 Unicode 字符并沒有包含在 XML 中。DOM 的實現(xiàn)使您可以添加它們,但后果是無法序列化。這些字符包括大多數(shù)的控制字符和 Unicode 代理對(surrogate pair)中的單個字符。只有您試圖在文檔中包含二進制數(shù)據(jù)時才會遇到這種情況,但這是另一種轉(zhuǎn)向(gotcha)情況。
![]() ![]() |
![]()
|
我已經(jīng)介紹了 DOM 能做的很多事情,但是 DOM(和 JavaScript)可以做的事情遠不止這些。仔細(xì)研究、揣摩這些例子,看看是如何使用它們來解決可能需要客戶端腳本、模板或?qū)S?API 的問題。
DOM 有自己的局限性和缺點,但同時也擁有眾多優(yōu)點:它內(nèi)置于很多應(yīng)用程序中;無論使用 Java 技術(shù)、Python 或 JavaScript,它都以相同方式工作;它非常便于使用 SAX;使用上述的模板,它使用起來既簡潔又強大。越來越多的應(yīng)用程序開始支持 DOM,這包括基于 Mozilla 的應(yīng)用程序、OpenOffice 和 Blast Radius 的 XMetaL。越來越多的規(guī)范需要 DOM,并對它加以擴展(例如,SVG),因此 DOM 時時刻刻就在您的身邊。使用這種被廣泛部署的工具,絕對是您的明智之舉。
![]() ![]() |
![]()
|
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
- 下載 JavaScript 庫,它包含了上面的腳本和一個用于測試這些腳本的簡單 測試頁面。
- 直接訪問 DOM 發(fā)源地 —— W3C 的 DOM 資源頁面 ,其中包含到所有與文檔對象模型相關(guān)的標(biāo)準(zhǔn)的鏈接。
- 查看 Jesse Ruderman 的 bookmarklets。雖然 Ruderman 沒有創(chuàng)造術(shù)語“bookmarkets”,但他收集了很多一流的、簡短的、書簽似的 JavaScript,使用它們開發(fā) DOM 的巨大潛力,使您的瀏覽器可以為您帶來更多幫助。
- 訪問 Sjoerd Visscher 的 Beyond JS 庫,它提供了遠遠超過我在這里提及的用于函數(shù)式編程的工具。如果您可以將事物抽象為函數(shù),那么 JavaScript 將會成為您得心應(yīng)手的工具。
- DOM API 的標(biāo)準(zhǔn)參考在 W3C。這里是 DOM2 到 JavaScript (ECMAScript)映射 的網(wǎng)址。
- 了解 AJAX 為什么已經(jīng)引起了這么大的反響。它使用了異步調(diào)用來使服務(wù)器實時升級 Web 應(yīng)用程序。您可以使用上述的許多技術(shù),并閱讀 異步通信工具。
- 了解一下 XML 編輯器和工具的 XMetaL 系列,它們都支持 DOM API。它們由作者所在的公司 Blast Radius 開發(fā)。
- 在 developerWorks 的 Developer Bookstore 了解更多 XMl 相關(guān)的書籍,其中包括 David Mertz 的 Text Processing in Python一書。
- 了解如何才能成為 IBM 認(rèn)證的 XML 及相關(guān)技術(shù)的開發(fā)人員。