| 2006 年 4 月 27 日 上一篇中 Brett 介紹了文檔對象模型(DOM),它的元素在幕后定義了 Web 頁(yè)面。這一期文章中他將進(jìn)一步探討 DOM。了解如何創(chuàng )建、刪除和修改 DOM 樹(shù)的各個(gè)部分,了解如何實(shí)現網(wǎng)頁(yè)的即時(shí)更新! 如果閱讀過(guò)本系列的 上一篇文章,那么您就非常清楚當 Web 瀏覽器顯示網(wǎng)頁(yè)時(shí)幕后發(fā)生的一切了。前面已經(jīng)提到,當 HTML 或為頁(yè)面定義的 CSS 發(fā)送給 Web 瀏覽器時(shí),網(wǎng)頁(yè)被從文本轉化成對象模型。無(wú)論代碼簡(jiǎn)單或復雜,集中到一個(gè)文件還是分散到多個(gè)文件,都是如此。然后瀏覽器直接使用對象模型而不是您提供的文本文件。瀏覽器使用的模型稱(chēng)為文檔對象模型(Document Object Model,DOM)。它連接表示文檔中元素、屬性和文本的對象。HTML 和 CSS 中所有的樣式、值、甚至大部分空格都合并到該對象模型中。給定網(wǎng)頁(yè)的具體模型稱(chēng)為該頁(yè)面的 DOM 樹(shù)。 了解什么是 DOM 樹(shù),以及知道它如何表示 HTML 和 CSS 僅僅是控制 Web 頁(yè)面的第一步。接下來(lái)還需要了解如何處理 Web 頁(yè)面的 DOM 樹(shù)。比方說(shuō),如果向 DOM 樹(shù)中增加一個(gè)元素,這個(gè)元素就會(huì )立即出現在用戶(hù)的 Web 瀏覽器中 —— 不需要重新加載頁(yè)面。從 DOM 樹(shù)中刪除一些文本,那些文本就會(huì )從用戶(hù)屏幕上消失??梢酝ㄟ^(guò) DOM 修改用戶(hù)界面或者與用戶(hù)界面交互,這樣就提供了很強的編程能力和靈活性。一旦學(xué)會(huì )了如何處理 DOM 樹(shù),您就向實(shí)現豐富的、交互式動(dòng)態(tài)網(wǎng)站邁出了一大步。 注意,下面的討論以上一篇文章 “利用 DOM 進(jìn)行 Web 響應” 為基礎,如果沒(méi)有閱讀過(guò)那一期,請在繼續閱讀之前首先閱讀上一篇文章。 | 首字母縮寫(xiě)的拼讀問(wèn)題 從很多方面來(lái)說(shuō),文檔對象模型應該被稱(chēng)為文檔節點(diǎn)模型(Document Node Model,DNM)。當然,大多數人不知道節點(diǎn) 一詞的含義,而且 “DNM” 也不像 “DOM” 那么容易拼讀,所以很容易理解 W3C 為何選擇了 DOM。 | | 跨瀏覽器、跨語(yǔ)言 文檔對象模型是一種 W3C 標準(鏈接參見(jiàn) 參考資料)。因此,所有現代 Web 瀏覽器都支持 DOM —— 至少在一定程度上支持。雖然不同的瀏覽器有一些區別,但如果使用 DOM 核心功能并注意少數特殊情況和例外,DOM 代碼就能以同樣的方式用于任何瀏覽器。修改 Opera 網(wǎng)頁(yè)的代碼同樣能用于 Apple‘s Safari?、Firefox?、Microsoft? Internet Explorer? 和 Mozilla?。 DOM 也是一種跨語(yǔ)言 的規范,換句話(huà)說(shuō),大多數主流編程語(yǔ)言都能使用它。W3C 為 DOM 定義了幾種語(yǔ)言綁定。一種語(yǔ)言綁定就是為特定語(yǔ)言定義的讓您使用 DOM 的 API。比如,可以使用為 C、Java 和 JavaScript 定義的 DOM 語(yǔ)言綁定。因此可以從這些語(yǔ)言中使用 DOM。還有幾種用于其他語(yǔ)言的語(yǔ)言綁定,盡管很多是由 W3C 以外的第三方定義的。 本系列文章主要討論 JavaScript 的 DOM 綁定。這是因為多數異步應用程序開(kāi)發(fā)都需要編寫(xiě)在 Web 瀏覽器中運行的 JavaScript 代碼。使用 JavaScript 和 DOM 可以即時(shí)修改用戶(hù)界面、響應用戶(hù)事件和輸入等等 —— 使用的完全是標準的 JavaScript。 總之,建議您也嘗試一下其他語(yǔ)言中的 DOM 綁定。比如,使用 Java 語(yǔ)言綁定不僅能處理 HTML 還可處理 XML,這些內容將在以后的文章中討論。因此本文介紹的技術(shù)還可用于 HTML 之外的其他語(yǔ)言,客戶(hù)端 JavaScript 之外的其他環(huán)境。
節點(diǎn)的概念 節點(diǎn)是 DOM 中最基本的對象類(lèi)型。實(shí)際上,您將在本文中看到,基本上 DOM 定義的其他所有對象都是節點(diǎn)對象的擴展。但是在深入分析語(yǔ)義之前,必須了解節點(diǎn)所代表的概念,然后再學(xué)習節點(diǎn)的具體屬性和方法就非常簡(jiǎn)單了。 在 DOM 樹(shù)中,基本上一切都是節點(diǎn)。每個(gè)元素在最底層上都是 DOM 樹(shù)中的節點(diǎn)。每個(gè)屬性都是節點(diǎn)。每段文本都是節點(diǎn)。甚至注釋、特殊字符(如版權符號 ?)、DOCTYPE 聲明(如果 HTML 或者 XHTML 中有的話(huà))全都是節點(diǎn)。因此在討論這些具體的類(lèi)型之前必須清楚地把握什么是節點(diǎn)。 節點(diǎn)是…… 用最簡(jiǎn)單的話(huà)說(shuō),DOM 樹(shù)中的任何事物都是節點(diǎn) 。之所以用 “事物” 這個(gè)模糊的字眼,是因為只能明確到這個(gè)程度。比如 HTML 中的元素(如 img)和 HTML 中的文本片段(如 “Scroll down for more details”)沒(méi)有多少明顯的相似之處。但這是因為您考慮的可能是每種類(lèi)型的功能,關(guān)注的是它們的不同點(diǎn)。 但是如果從另一個(gè)角度觀(guān)察,DOM 樹(shù)中的每個(gè)元素和每段文本都有一個(gè)父親,這個(gè)父節點(diǎn)可能是另一個(gè)元素(比如嵌套在 p 元素中的 img)的孩子,或者 DOM 樹(shù)中的頂層元素(這是每個(gè)文檔中都出現一次的特殊情況,即使用 html 元素的地方)。另外,元素和文本都有一個(gè)類(lèi)型。顯然,元素的類(lèi)型就是元素,文本的類(lèi)型就是文本。每個(gè)節點(diǎn)還有某種定義明確的結構:下面還有節點(diǎn)(如子元素)嗎?有兄弟節點(diǎn)(與元素或文本 “相鄰的” 節點(diǎn))嗎?每個(gè)節點(diǎn)屬于哪個(gè)文檔? 顯然,大部分內容聽(tīng)起來(lái)很抽象。實(shí)際上,說(shuō)一個(gè)元素的類(lèi)型是元素似乎有點(diǎn)冒傻氣。但是要真正認識到將節點(diǎn)作為通用對象類(lèi)型的價(jià)值,必須抽象一點(diǎn)來(lái)思考。 通用節點(diǎn)類(lèi)型 DOM 代碼中最常用的任務(wù)就是在頁(yè)面的 DOM 樹(shù)中導航。比方說(shuō),可以通過(guò)其 “id” 屬性定位一個(gè) form,然后開(kāi)始處理那個(gè) form 中內嵌的元素和文本。其中可能包含文字說(shuō)明、輸入字段的標簽、真正的 input 元素,以及其他 HTML 元素(如 img)和鏈接(a 元素)。如果元素和文本是完全不同的類(lèi)型,就必須為每種類(lèi)型編寫(xiě)完全不同的代碼。 如果使用一種通用節點(diǎn)類(lèi)型情況就不同了。這時(shí)候只需要從一個(gè)節點(diǎn)移動(dòng)到另一個(gè)節點(diǎn),只有當需要對元素或文本作某種特殊處理時(shí)才需要考慮節點(diǎn)的類(lèi)型。如果僅僅在 DOM 樹(shù)中移動(dòng),就可以與其他節點(diǎn)類(lèi)型一樣用同樣的操作移動(dòng)到元素的父節點(diǎn)或者子節點(diǎn)。只有當需要某種節點(diǎn)類(lèi)型的特殊性質(zhì)時(shí),如元素的屬性,才需要對節點(diǎn)類(lèi)型作專(zhuān)門(mén)處理。將 DOM 樹(shù)中的所有對象都看作節點(diǎn)可以簡(jiǎn)化操作。記住這一點(diǎn)之后,接下來(lái)我們將具體看看 DOM 節點(diǎn)構造應該提供什么,首先從屬性和方法開(kāi)始。
節點(diǎn)的屬性 使用 DOM 節點(diǎn)時(shí)需要一些屬性和方法,因此我們首先來(lái)討論節點(diǎn)的屬性和方法。DOM 節點(diǎn)的屬性主要有: nodeName 報告節點(diǎn)的名稱(chēng)(詳見(jiàn)下述)。 nodeValue 提供節點(diǎn)的 “值”(詳見(jiàn)后述)。 parentNode 返回節點(diǎn)的父節點(diǎn)。記住,每個(gè)元素、屬性和文本都有一個(gè)父節點(diǎn)。 childNodes 是節點(diǎn)的孩子節點(diǎn)列表。對于 HTML,該列表僅對元素有意義,文本節點(diǎn)和屬性節點(diǎn)都沒(méi)有孩子。 firstChild 僅僅是 childNodes 列表中第一個(gè)節點(diǎn)的快捷方式。 lastChild 是另一種快捷方式,表示 childNodes 列表中的最后一個(gè)節點(diǎn)。 previousSibling 返回當前節點(diǎn)之前 的節點(diǎn)。換句話(huà)說(shuō),它返回當前節點(diǎn)的父節點(diǎn)的 childNodes 列表中位于該節點(diǎn)前面的那個(gè)節點(diǎn)(如果感到迷惑,重新讀前面一句)。 nextSibling 類(lèi)似于 previousSibling 屬性,返回父節點(diǎn)的 childNodes 列表中的下一個(gè)節點(diǎn)。 attributes 僅用于元素節點(diǎn),返回元素的屬性列表。 其他少數幾種屬性實(shí)際上僅用于更一般的 XML 文檔,在處理基于 HTML 的網(wǎng)頁(yè)時(shí)沒(méi)有多少用處。 不常用的屬性 上述大部分屬性的意義都很明確,除了 nodeName 和 nodeValue 屬性以外。我們不是簡(jiǎn)單地解釋這兩個(gè)屬性,而是提出兩個(gè)奇怪的問(wèn)題:文本節點(diǎn)的 nodeName 應該是什么?類(lèi)似地,元素的 nodeValue 應該是什么? 如果這些問(wèn)題難住了您,那么您就已經(jīng)了解了這些屬性固有的含糊性。nodeName 和 nodeValue 實(shí)際上并非適用于所有 節點(diǎn)類(lèi)型(節點(diǎn)的其他少數幾個(gè)屬性也是如此)。這就說(shuō)明了一個(gè)重要概念:任何這些屬性都可能返回空值(有時(shí)候在 JavaScript 中稱(chēng)為 “未定義”)。比方說(shuō),文本節點(diǎn)的 nodeName 屬性是空值(或者在一些瀏覽器中稱(chēng)為 “未定義”),因為文本節點(diǎn)沒(méi)有名稱(chēng)。如您所料,nodeValue 返回節點(diǎn)的文本。 類(lèi)似地,元素有 nodeName,即元素名,但元素的 nodeValue 屬性值總是空。屬性同時(shí)具有 nodeName 和 nodeValue。下一節我還將討論這些單獨的類(lèi)型,但是因為這些屬性是每個(gè)節點(diǎn)的一部分,因此在這里有必要提一提。 現在看看 清單 1,它用到了一些節點(diǎn)屬性。 清單 1. 使用 DOM 中的節點(diǎn)屬性 // These first two lines get the DOM tree for the current Web page, // and then the <html> element for that DOM tree var myDocument = document; var htmlElement = myDocument.documentElement; // What‘s the name of the <html> element? "html" alert("The root element of the page is " + htmlElement.nodeName); // Look for the <head> element var headElement = htmlElement.getElementsByTagName("head")[0]; if (headElement != null) { alert("We found the head element, named " + headElement.nodeName); // Print out the title of the page var titleElement = headElement.getElementsByTagName("title")[0]; if (titleElement != null) { // The text will be the first child node of the <title> element var titleText = titleElement.firstChild; // We can get the text of the text node with nodeValue alert("The page title is ‘" + titleText.nodeValue + "‘"); } // After <head> is <body> var bodyElement = headElement.nextSibling; while (bodyElement.nodeName.toLowerCase() != "body") { bodyElement = bodyElement.nextSibling; } // We found the <body> element... // We‘ll do more when we know some methods on the nodes. } |
節點(diǎn)方法 接下來(lái)看看所有節點(diǎn)都具有的方法(與節點(diǎn)屬性一樣,我省略了實(shí)際上不適用于多數 HTML DOM 操作的少數方法): insertBefore(newChild, referenceNode) 將 newChild 節點(diǎn)插入到 referenceNode 之前。記住,應該對 newChild 的目標父節點(diǎn)調用該方法。 replaceChild(newChild, oldChild) 用 newChild 節點(diǎn)替換 oldChild 節點(diǎn)。 removeChild(oldChild) 從運行該方法的節點(diǎn)中刪除 oldChild 節點(diǎn)。 appendChild(newChild) 將 newChild 添加到運行該函數的節點(diǎn)之中。newChild 被添加到目標節點(diǎn)孩子列表中的末端。 hasChildNodes() 在調用該方法的節點(diǎn)有孩子時(shí)則返回 true,否則返回 false。 hasAttributes() 在調用該方法的節點(diǎn)有屬性時(shí)則返回 true,否則返回 false。 注意,大部分情況下所有這些方法處理的都是節點(diǎn)的孩子。這是它們的主要用途。如果僅僅想獲取文本節點(diǎn)值或者元素名,則不需要調用這些方法,使用節點(diǎn)屬性就可以了。清單 2 在 清單 1 的基礎上增加了方法使用。 清單 2. 使用 DOM 中的節點(diǎn)方法 // These first two lines get the DOM tree for the current Web page, // and then the <html> element for that DOM tree var myDocument = document; var htmlElement = myDocument.documentElement; // What‘s the name of the <html> element? "html" alert("The root element of the page is " + htmlElement.nodeName); // Look for the <head> element var headElement = htmlElement.getElementsByTagName("head")[0]; if (headElement != null) { alert("We found the head element, named " + headElement.nodeName); // Print out the title of the page var titleElement = headElement.getElementsByTagName("title")[0]; if (titleElement != null) { // The text will be the first child node of the <title> element var titleText = titleElement.firstChild; // We can get the text of the text node with nodeValue alert("The page title is ‘" + titleText.nodeValue + "‘"); } // After <head> is <body> var bodyElement = headElement.nextSibling; while (bodyElement.nodeName.toLowerCase() != "body") { bodyElement = bodyElement.nextSibling; } // We found the <body> element... // Remove all the top-level <img> elements in the body if (bodyElement.hasChildNodes()) { for (i=0; i<bodyElement.childNodes.length; i++) { var currentNode = bodyElement.childNodes[i]; if (currentNode.nodeName.toLowerCase() == "img") { bodyElement.removeChild(currentNode); } } } } | 測試一下! 目前雖然只看到了兩個(gè)例子,清單 1 和 2,不過(guò)通過(guò)這兩個(gè)例子您應該能夠了解使用 DOM 樹(shù)能夠做什么。如果要嘗試一下這些代碼,只需要將 清單 3 拖入一個(gè) HTML 文件并保存,然后用 Web 瀏覽器打開(kāi)。 清單 3. 包含使用 DOM 的 JavaScript 代碼的 HTML 文件 <html> <head> <title>JavaScript and the DOM</title> <script language="JavaScript"> function test() { // These first two lines get the DOM tree for the current Web page, // and then the <html> element for that DOM tree var myDocument = document; var htmlElement = myDocument.documentElement; // What‘s the name of the <html> element? "html" alert("The root element of the page is " + htmlElement.nodeName); // Look for the <head> element var headElement = htmlElement.getElementsByTagName("head")[0]; if (headElement != null) { alert("We found the head element, named " + headElement.nodeName); // Print out the title of the page var titleElement = headElement.getElementsByTagName("title")[0]; if (titleElement != null) { // The text will be the first child node of the <title> element var titleText = titleElement.firstChild; // We can get the text of the text node with nodeValue alert("The page title is ‘" + titleText.nodeValue + "‘"); } // After <head> is <body> var bodyElement = headElement.nextSibling; while (bodyElement.nodeName.toLowerCase() != "body") { bodyElement = bodyElement.nextSibling; } // We found the <body> element... // Remove all the top-level <img> elements in the body if (bodyElement.hasChildNodes()) { for (i=0; i<bodyElement.childNodes.length; i++) { var currentNode = bodyElement.childNodes[i]; if (currentNode.nodeName.toLowerCase() == "img") { bodyElement.removeChild(currentNode); } } } } } </script> </head> <body> <p>JavaScript and DOM are a perfect match. You can read more in <i>Head Rush Ajax</i>.</p> <img src="http://www.headfirstlabs.com/Images/hraj_cover-150.jpg" /> <input type="button" value="Test me!" onClick="test();" /> </body> </html> | 將該頁(yè)面加載到瀏覽器后,可以看到類(lèi)似 圖 1 所示的畫(huà)面。 圖 1. 用按鈕運行 JavaScript 的 HTML 頁(yè)面
單擊 Test me! 將看到 圖 2 所示的警告框。 圖 2. 使用 nodeValue 顯示元素名的警告框
代碼運行完成后,圖片將從頁(yè)面中實(shí)時(shí)刪除,如 圖 3 所示。 圖 3. 使用 JavaScript 實(shí)時(shí)刪除圖像
API 設計問(wèn)題 再看一看各種節點(diǎn)提供的屬性和方法。對于那些熟悉面向對象(OO)編程的人來(lái)說(shuō),它們說(shuō)明了 DOM 的一個(gè)重要特點(diǎn):DOM 并非完全面向對象的 API。首先,很多情況下要直接使用對象的屬性而不是調用節點(diǎn)對象的方法。比方說(shuō),沒(méi)有 getNodeName() 方法,而要直接使用 nodeName 屬性。因此節點(diǎn)對象(以及其他 DOM 對象)通過(guò)屬性而不是函數公開(kāi)了大量數據。 其次,如果習慣于使用重載對象和面向對象的 API,特別是 Java 和 C++ 這樣的語(yǔ)言,就會(huì )發(fā)現 DOM 中的對象和方法命名有點(diǎn)奇怪。DOM 必須能用于 C、Java 和 JavaScript(這只是其中的幾種語(yǔ)言),因此 API 設計作了一些折衷。比如,NamedNodeMap 方法有兩種不同的形式: getNamedItem(String name) getNamedItemNS(Node node) 對于 OO 程序員來(lái)說(shuō)這看起來(lái)非常奇怪。兩個(gè)方法目的相同,只不過(guò)一個(gè)使用 String 參數而另一個(gè)使用 Node 參數。多數 OO API 中對這兩種版本都會(huì )使用相同的方法名。運行代碼的虛擬機將根據傳遞給方法的對象類(lèi)型決定運行哪個(gè)方法。 問(wèn)題在于 JavaScript 不支持這種稱(chēng)為方法重載 的技術(shù)。換句話(huà)說(shuō),JavaScript 要求每個(gè)方法或函數使用不同的名稱(chēng)。因此,如果有了一個(gè)名為 getNamedItem() 的接受字符串參數的方法,就不能再有另一個(gè)方法或函數也命名為 getNamedItem(),即使這個(gè)方法的參數類(lèi)型不同(或者完全不同的一組參數)。如果這樣做,JavaScript 將報告錯誤,代碼不會(huì )按照預期的方式執行。 從根本上說(shuō),DOM 有意識地避開(kāi)了方法重載和其他 OO 編程技術(shù)。這是為了保證該 API 能夠用于多種語(yǔ)言,包括那些不支持 OO 編程技術(shù)的語(yǔ)言。后果不過(guò)是要求您多記住一些方法名而已。好處是可以在任何語(yǔ)言中學(xué)習 DOM,比如 Java,并清楚同樣的方法名和編碼結構也能用于具有 DOM 實(shí)現的其他語(yǔ)言,如 JavaScript。 讓程序員小心謹慎 如果深入研究 API 設計或者僅僅非常關(guān)注 API 設計,您可能會(huì )問(wèn):“為何節點(diǎn)類(lèi)型的屬性不能適用于所有節點(diǎn)?” 這是一個(gè)很好的問(wèn)題,問(wèn)題的答案與政治及決策關(guān)系更密切,而非技術(shù)原因。簡(jiǎn)單地說(shuō),答案就是,“誰(shuí)知道!但有點(diǎn)令人惱火,不是嗎?” 屬性 nodeName 意味著(zhù)允許每種類(lèi)型的節點(diǎn)都有一個(gè)名字,但是很多情況下名字要么未定義,要么是對于程序員沒(méi)有意義的內部名(比如在 Java 中,很多情況下文本節點(diǎn)的 nodeName 被報告為 “#text”)。從根本上說(shuō),必須假設您得自己來(lái)處理錯誤。直接訪(fǎng)問(wèn) myNode.nodeName 然后使用該值是危險的,很多情況下這個(gè)值為空。因此與通常的編程一樣,程序員要謹慎從事。
通用節點(diǎn)類(lèi)型 現在已經(jīng)介紹了 DOM 節點(diǎn)的一些特性和屬性(以及一些奇特的地方),下面開(kāi)始講述您將用到的一些特殊節點(diǎn)類(lèi)型。多數 Web 應用程序中只用到四種節點(diǎn)類(lèi)型: - 文檔節點(diǎn)表示整個(gè) HTML 文檔。
- 元素節點(diǎn)表示 HTML 元素,如
a 或 img。 - 屬性節點(diǎn)表示 HTML 元素的屬性,如
href(a 元素)或 src(img 元素)。 - 文本節點(diǎn)表示 HTML 文檔中的文本,如 “Click on the link below for a complete set list”。這是出現在
p、a 或 h2 這些元素中的文字。 處理 HTML 時(shí),95% 的時(shí)間是跟這些節點(diǎn)類(lèi)型打交道。因此本文的其余部分將詳細討論這些節點(diǎn)。(將來(lái)討論 XML 的時(shí)候將介紹其他一些節點(diǎn)類(lèi)型。)
文檔節點(diǎn) 基本上所有基于 DOM 的代碼中都要用到的第一個(gè)節點(diǎn)類(lèi)型是文檔節點(diǎn)。文檔節點(diǎn) 實(shí)際上并不是 HTML(或 XML)頁(yè)面中的一個(gè)元素而是頁(yè)面本身。因此在 HTML Web 頁(yè)面中,文檔節點(diǎn)就是整個(gè) DOM 樹(shù)。在 JavaScript 中,可以使用關(guān)鍵字 document 訪(fǎng)問(wèn)文檔節點(diǎn): // These first two lines get the DOM tree for the current Web page, // and then the <html> element for that DOM tree var myDocument = document; var htmlElement = myDocument.documentElement; | JavaScript 中的 document 關(guān)鍵字返回當前網(wǎng)頁(yè)的 DOM 樹(shù)。從這里可以開(kāi)始處理樹(shù)中的所有節點(diǎn)。 也可使用 document 對象創(chuàng )建新節點(diǎn),如下所示: createElement(elementName) 使用給定的名稱(chēng)創(chuàng )建一個(gè)元素。 createTextNode(text) 使用提供的文本創(chuàng )建一個(gè)新的文本節點(diǎn)。 createAttribute(attributeName) 用提供的名稱(chēng)創(chuàng )建一個(gè)新屬性。 這里的關(guān)鍵在于這些方法創(chuàng )建節點(diǎn),但是并沒(méi)有將其附加或者插入到特定的文檔中。因此,必須使用前面所述的方法如 insertBefore() 或 appendChild() 來(lái)完成這一步。因此,可使用下面的代碼創(chuàng )建新元素并將其添加到文檔中: var pElement = myDocument.createElement("p"); var text = myDocument.createTextNode("Here‘s some text in a p element."); pElement.appendChild(text); bodyElement.appendChild(pElement); | 一旦使用 document 元素獲得對 Web 頁(yè)面 DOM 樹(shù)的訪(fǎng)問(wèn),就可以直接使用元素、屬性和文本了。
元素節點(diǎn) 雖然會(huì )大量使用元素節點(diǎn),但很多需要對元素執行的操作都是所有節點(diǎn)共有的方法和屬性,而不是元素特有的方法和屬性。元素只有兩組專(zhuān)有的方法: - 與屬性處理有關(guān)的方法:
getAttribute(name) 返回名為 name 的屬性值。 removeAttribute(name) 刪除名為 name 的屬性。 setAttribute(name, value) 創(chuàng )建一個(gè)名為 name 的屬性并將其值設為 value。 getAttributeNode(name) 返回名為 name 的屬性節點(diǎn)(屬性節點(diǎn)在 下一節 介紹)。 removeAttributeNode(node) 刪除與指定節點(diǎn)匹配的屬性節點(diǎn)。 - 與查找嵌套元素有關(guān)的方法:
getElementsByTagName(elementName) 返回具有指定名稱(chēng)的元素節點(diǎn)列表。 這些方法意義都很清楚,但還是來(lái)看幾個(gè)例子吧。 處理屬性 處理元素很簡(jiǎn)單,比如可用 document 對象和上述方法創(chuàng )建一個(gè)新的 img 元素: var imgElement = document.createElement("img"); imgElement.setAttribute("src", "http://www.headfirstlabs.com/Images/hraj_cover-150.jpg"); imgElement.setAttribute("width", "130"); imgElement.setAttribute("height", "150"); bodyElement.appendChild(imgElement); | 現在看起來(lái)應該非常簡(jiǎn)單了。實(shí)際上,只要理解了節點(diǎn)的概念并知道有哪些方法可用,就會(huì )發(fā)現在 Web 頁(yè)面和 JavaScript 代碼中處理 DOM 非常簡(jiǎn)單。在上述代碼中,JavaScript 創(chuàng )建了一個(gè)新的 img 元素,設置了一些屬性然后添加到 HTML 頁(yè)面的 body 元素中。 查找嵌套元素 發(fā)現嵌套的元素很容易。比如,下面的代碼用于發(fā)現和刪除 清單 3 所示 HTML 頁(yè)面中的所有 img 元素: // Remove all the top-level <img> elements in the body if (bodyElement.hasChildNodes()) { for (i=0; i<bodyElement.childNodes.length; i++) { var currentNode = bodyElement.childNodes[i]; if (currentNode.nodeName.toLowerCase() == "img") { bodyElement.removeChild(currentNode); } } } | 也可以使用 getElementsByTagName() 完成類(lèi)似的功能: // Remove all the top-level <img> elements in the body var imgElements = bodyElement.getElementsByTagName("img"); for (i=0; i<imgElements.length; i++) { var imgElement = imgElements.item[i]; bodyElement.removeChild(imgElement); } |
屬性節點(diǎn) DOM 將屬性表示成節點(diǎn),可以通過(guò)元素的 attributes 來(lái)訪(fǎng)問(wèn)元素的屬性,如下所示: // Remove all the top-level <img> elements in the body var imgElements = bodyElement.getElementsByTagName("img"); for (i=0; i<imgElements.length; i++) { var imgElement = imgElements.item[i]; // Print out some information about this element var msg = "Found an img element!"; var atts = imgElement.attributes; for (j=0; j<atts.length; j++) { var att = atts.item(j); msg = msg + "\n " + att.nodeName + ": ‘" + att.nodeValue + "‘"; } alert(msg); bodyElement.removeChild(imgElement); } | | 屬性的奇特之處 對于 DOM 來(lái)說(shuō)屬性有一些特殊的地方。一方面,屬性實(shí)際上并不像其他元素或文本那樣是元素的孩子,換句話(huà)說(shuō),屬性并不出現在元素 “之下”。同時(shí),屬性顯然和元素有一定的關(guān)系,元素 “擁有” 屬性。DOM 使用節點(diǎn)表示屬性,并允許通過(guò)元素的專(zhuān)門(mén)列表來(lái)訪(fǎng)問(wèn)屬性。因此屬性是 DOM 樹(shù)的一部分,但通常不出現在樹(shù)中。有理由說(shuō),屬性和 DOM 樹(shù)結構其他部分之間的關(guān)系有點(diǎn)模糊。 | | 需要指出的是,attributes 屬性實(shí)際上是對節點(diǎn)類(lèi)型而非局限于元素類(lèi)型來(lái)說(shuō)的。有點(diǎn)古怪,不影響您編寫(xiě)代碼,但是仍然有必要知道這一點(diǎn)。 雖然也能使用屬性節點(diǎn),但通常使用元素類(lèi)的方法處理屬性更簡(jiǎn)單。其中包括: getAttribute(name) 返回名為 name 的屬性值。 removeAttribute(name) 刪除名為 name 的屬性。 setAttribute(name, value) 創(chuàng )建一個(gè)名為 name 的屬性并將其值設為 value。 這三個(gè)方法不需要直接處理屬性節點(diǎn)。但允許使用簡(jiǎn)單的字符串屬性設置和刪除屬性及其值。
文本節點(diǎn) 需要考慮的最后一種節點(diǎn)是文本節點(diǎn)(至少在處理 HTML DOM 樹(shù)的時(shí)候如此)?;旧贤ǔS糜谔幚砦谋竟濣c(diǎn)的所有屬性都屬于節點(diǎn)對象。實(shí)際上,一般使用 nodeValue 屬性來(lái)訪(fǎng)問(wèn)文本節點(diǎn)的文本,如下所示: var pElements = bodyElement.getElementsByTagName("p"); for (i=0; i<pElements.length; i++) { var pElement = pElements.item(i); var text = pElement.firstChild.nodeValue; alert(text); } | 少數其他幾種方法是專(zhuān)門(mén)用于文本節點(diǎn)的。這些方法用于增加或分解節點(diǎn)中的數據: appendData(text) 將提供的文本追加到文本節點(diǎn)的已有內容之后。 insertData(position, text) 允許在文本節點(diǎn)的中間插入數據。在指定的位置插入提供的文本。 replaceData(position, length, text) 從指定位置開(kāi)始刪除指定長(cháng)度的字符,用提供的文本代替刪除的文本。
什么節點(diǎn)類(lèi)型? 到目前為止看到的多數代碼都假設已經(jīng)知道處理的節點(diǎn)是什么類(lèi)型,但情況并非總是如此。比方說(shuō),如果在 DOM 樹(shù)中導航并處理一般的節點(diǎn)類(lèi)型,可能就不知道您遇到了元素還是文本。也許獲得了 p 元素的所有孩子,但是不能確定處理的是文本、b 元素還是 img 元素。這種情況下,在進(jìn)一步的處理之前需要確定是什么類(lèi)型的節點(diǎn)。 所幸的是很容易就能做到。DOM 節點(diǎn)類(lèi)型定義了一些常量,比如: Node.ELEMENT_NODE 是表示元素節點(diǎn)類(lèi)型的常量。 Node.ATTRIBUTE_NODE 是表示屬性節點(diǎn)類(lèi)型的常量。 Node.TEXT_NODE 是表示文本節點(diǎn)類(lèi)型的常量。 Node.DOCUMENT_NODE 是表示文檔節點(diǎn)類(lèi)型的常量。 還有其他一些節點(diǎn)類(lèi)型,但是對于 HTML 除了這四種以外很少用到。我有意沒(méi)有給出這些常量的值,雖然 DOM 規范中定義了這些值,永遠不要直接使用那些值,因為這正是常量的目的! nodeType 屬性 可使用 nodeType 屬性比較節點(diǎn)和上述常量 —— 該屬性定義在 DOM node 類(lèi)型上因此可用于所有節點(diǎn),如下所示: var someNode = document.documentElement.firstChild; if (someNode.nodeType == Node.ELEMENT_NODE) { alert("We‘ve found an element node named " + someNode.nodeName); } else if (someNode.nodeType == Node.TEXT_NODE) { alert("It‘s a text node; the text is " + someNode.nodeValue); } else if (someNode.nodeType == Node.ATTRIBUTE_NODE) { alert("It‘s an attribute named " + someNode.nodeName + " with a value of ‘" + someNode.nodeValue + "‘"); } | 這個(gè)例子非常簡(jiǎn)單,但說(shuō)明了一個(gè)大問(wèn)題:得到節點(diǎn)的類(lèi)型非常 簡(jiǎn)單。更有挑戰性的是知道節點(diǎn)的類(lèi)型之后確定能做什么,只要掌握了節點(diǎn)、文本、屬性和元素類(lèi)型提供了什么屬性和方法,就可以自己進(jìn)行 DOM 編程了。 好了,快結束了。 實(shí)踐中的挫折 nodeType 屬性似乎是使用節點(diǎn)的一個(gè)入場(chǎng)券 —— 允許確定要處理的節點(diǎn)類(lèi)型然后編寫(xiě)處理該節點(diǎn)的代碼。問(wèn)題在于上述 Node 常量定義不能正確地用于 Internet Explorer。因此如果在代碼中使用 Node.ELEMENT_NODE、Node.TEXT_NODE 或其他任何常量,Internet Explorer 都將返回如 圖 4 所示的錯誤。 圖 4. Internet Explorer 報告錯誤
任何時(shí)候在 JavaScript 中使用 Node 常量,Internet Explorer 都會(huì )報錯。因為多數人仍然在使用 Internet Explorer,應該避免在代碼中使用 Node.ELEMENT_NODE 或 Node.TEXT_NODE 這樣的構造。盡管據說(shuō)即將發(fā)布的新版本 Internet Explorer 7.0 將解決這個(gè)問(wèn)題,但是在 Internet Explorer 6.x 退出舞臺之前仍然要很多年。因此應避免使用 Node,要想讓您的 DOM 代碼(和 Ajax 應用程序)能用于所有主要瀏覽器,這一點(diǎn)很重要。
結束語(yǔ) | 準備成為頂尖的網(wǎng)頁(yè)設計師嗎? 如果您準備了解甚至掌握 DOM,您就會(huì )成為最頂尖的 Web 編程人員。多數 Web 程序員知道如何使用 JavaScript 編寫(xiě)圖像滾動(dòng)或者從表單中提取值,有些甚至能夠向服務(wù)器發(fā)送請求和接收響應(閱讀本系列的前幾篇文章之后您也能做到)。但膽小鬼或者沒(méi)有經(jīng)驗的人不可能做到即時(shí)修改網(wǎng)頁(yè)結構。 | | 在本系列的上幾期文章中您已經(jīng)學(xué)習了很多?,F在,您不 應該再坐等下一篇文章期待我介紹各種聰明的 DOM 樹(shù)用法?,F在的家庭作業(yè)是看看如何使用 DOM 創(chuàng )造出富有想像力的效果或者漂亮的界面。利用近幾期文章中所學(xué)的知識開(kāi)始實(shí)驗和練習??纯茨芊窠⒏杏X(jué)更與桌面應用程序接近的網(wǎng)站,對象能夠響應用戶(hù)的動(dòng)作在屏幕上移動(dòng)。 最好在屏幕上為每個(gè)對象畫(huà)一個(gè)邊界,這樣就能看到 DOM 樹(shù)中的對象在何處,然后再移動(dòng)對象。創(chuàng )建節點(diǎn)并將其添加到已有的孩子列表中,刪除沒(méi)有嵌套節點(diǎn)的空節點(diǎn),改變節點(diǎn)的 CSS 樣式,看看孩子節點(diǎn)是否會(huì )繼承這些修改??赡苄允菬o(wú)限的,每當嘗試一些新東西時(shí),就學(xué)到了一些新的知識。盡情地修改您的網(wǎng)頁(yè)吧! 在 DOM 三部曲的最后一期文章中,我將 介紹如何把一些非常棒的有趣的 DOM 應用結合到編程中。我將不再是從概念上說(shuō)教和解釋 API,而會(huì )提供一些代碼。在此之前先發(fā)揮您自己的聰明才智,看看能做些什么。
參考資料 學(xué)習 獲得產(chǎn)品和技術(shù) - Brett McLaughlin 所著(zhù)的 Head Rush Ajax(O‘Reilly Media, Inc.,2006 年 3 月):深入淺出地將本文中的思想印入您的腦海。
- Java and XML, Second Edition(Brett McLaughlin,2001 年 8 月,O‘Reilly Media, Inc.):看看作者關(guān)于 XHTML 和 XML 轉換的討論。
- JavaScript: The Definitive Guide(David Flanagan,2001 年 11 月,O‘Reilly Media, Inc.):深入了解關(guān)于使用 JavaScript 和動(dòng)態(tài) Web 頁(yè)面的各種建議。下一版將增加關(guān)于 Ajax 的兩章。
- Head First HTML with CSS & XHTML(Elizabeth 與 Eric Freeman,2005 年 12 月,O‘Reilly Media, Inc.):進(jìn)一步了解標準 HTML 和 XHTML,以及如何將 CSS 應用于 HTML。
- IBM 試用軟件:用這些軟件開(kāi)發(fā)您的下一個(gè)項目,可直接從 developerWorks 下載。
討論
關(guān)于作者 | | | | Brett McLaughlin 從 Log 時(shí)代就開(kāi)始使用計算機了。(還記得那個(gè)小三角嗎?)近年來(lái),他已經(jīng)成為 Java 和 XML 社區中最受歡迎的作者和程序員之一了。他曾經(jīng)在 Nextel Communications 實(shí)現過(guò)復雜的企業(yè)系統,在 Lutris Technologies 編寫(xiě)過(guò)應用服務(wù)器,最近在 O‘Reilly Media, Inc. 繼續撰寫(xiě)和編輯這方面的圖書(shū)。Brett 最新的著(zhù)作 Head Rush Ajax,為 Ajax 帶來(lái)了獲獎的創(chuàng )新 Head First 方法。他的近作 Java 1.5 Tiger: A Developer‘s Notebook 是關(guān)于這一 Java 技術(shù)最新版本的第一部專(zhuān)著(zhù)。經(jīng)典作品 Java and XML 仍然是在 Java 語(yǔ)言中使用 XML 技術(shù)的權威著(zhù)作之一。 |
|