掌握 Ajax,第 5 部分: 操縱 DOM

使用 JavaScript 即時更新 Web 頁面

developerWorks
文檔選項
將此頁作爲電子郵件發送

將此頁作爲電子郵件發送



級別: 初級

Brett McLaughlin ([email protected]), 作家,編輯, O'Reilly Media Inc.

2006 年 4 月 27 日

上一期中 Brett 介紹了文檔對象模型(DOM),它的元素在幕後定義了 Web 頁面。這一期文章中他將進一步探討 DOM。瞭解如何創建、刪除和修改 DOM 樹的各個部分,瞭解如何實現網頁的即時更新!

如果閱讀過本系列的 上一期文章,那麼您就非常清楚當 Web 瀏覽器顯示網頁時幕後發生的一切了。前面已經提到,當 HTML 或爲頁面定義的 CSS 發送給 Web 瀏覽器時,網頁被從文本轉化成對象模型。無論代碼簡單或複雜,集中到一個文件還是分散到多個文件,都是如此。然後瀏覽器直接使用對象模型而不是您提供的文本文件。瀏覽器使用的模型稱爲文檔對象模型(Document Object Model,DOM)。它連接表示文檔中元素、屬性和文本的對象。HTML 和 CSS 中所有的樣式、值、甚至大部分空格都合併到該對象模型中。給定網頁的具體模型稱爲該頁面的 DOM 樹

瞭解什麼是 DOM 樹,以及知道它如何表示 HTML 和 CSS 僅僅是控制 Web 頁面的第一步。接下來還需要了解如何處理 Web 頁面的 DOM 樹。比方說,如果向 DOM 樹中增加一個元素,這個元素就會立即出現在用戶的 Web 瀏覽器中 —— 不需要重新加載頁面。從 DOM 樹中刪除一些文本,那些文本就會從用戶屏幕上消失。可以通過 DOM 修改用戶界面或者與用戶界面交互,這樣就提供了很強的編程能力和靈活性。一旦學會了如何處理 DOM 樹,您就向實現豐富的、交互式動態網站邁出了一大步。

注意,下面的討論以上一期文章 “利用 DOM 進行 Web 響應” 爲基礎,如果沒有閱讀過那一期,請在繼續閱讀之前首先閱讀上一期文章。

首字母縮寫的拼讀問題

從很多方面來說,文檔對象模型應該被稱爲文檔節點模型(Document Node Model,DNM)。當然,大多數人不知道節點 一詞的含義,而且 “DNM” 也不像 “DOM” 那麼容易拼讀,所以很容易理解 W3C 爲何選擇了 DOM。

跨瀏覽器、跨語言

文檔對象模型是一種 W3C 標準(鏈接參見 參考資料)。因此,所有現代 Web 瀏覽器都支持 DOM —— 至少在一定程度上支持。雖然不同的瀏覽器有一些區別,但如果使用 DOM 核心功能並注意少數特殊情況和例外,DOM 代碼就能以同樣的方式用於任何瀏覽器。修改 Opera 網頁的代碼同樣能用於 Apple's Safari®、Firefox®、Microsoft® Internet Explorer® 和 Mozilla®。

DOM 也是一種跨語言 的規範,換句話說,大多數主流編程語言都能使用它。W3C 爲 DOM 定義了幾種語言綁定。一種語言綁定就是爲特定語言定義的讓您使用 DOM 的 API。比如,可以使用爲 C、Java 和 JavaScript 定義的 DOM 語言綁定。因此可以從這些語言中使用 DOM。還有幾種用於其他語言的語言綁定,儘管很多是由 W3C 以外的第三方定義的。

本系列文章主要討論 JavaScript 的 DOM 綁定。這是因爲多數異步應用程序開發都需要編寫在 Web 瀏覽器中運行的 JavaScript 代碼。使用 JavaScript 和 DOM 可以即時修改用戶界面、響應用戶事件和輸入等等 —— 使用的完全是標準的 JavaScript。

總之,建議您也嘗試一下其他語言中的 DOM 綁定。比如,使用 Java 語言綁定不僅能處理 HTML 還可處理 XML,這些內容將在以後的文章中討論。因此本文介紹的技術還可用於 HTML 之外的其他語言,客戶端 JavaScript 之外的其他環境。





回頁首


節點的概念

節點是 DOM 中最基本的對象類型。實際上,您將在本文中看到,基本上 DOM 定義的其他所有對象都是節點對象的擴展。但是在深入分析語義之前,必須瞭解節點所代表的概念,然後再學習節點的具體屬性和方法就非常簡單了。

在 DOM 樹中,基本上一切都是節點。每個元素在最底層上都是 DOM 樹中的節點。每個屬性都是節點。每段文本都是節點。甚至註釋、特殊字符(如版權符號 ©)、DOCTYPE 聲明(如果 HTML 或者 XHTML 中有的話)全都是節點。因此在討論這些具體的類型之前必須清楚地把握什麼是節點。

節點是……

用最簡單的話說,節點就是 DMO 樹中的任何事物。之所以用 “事物” 這個模糊的字眼,是因爲只能明確到這個程度。比如 HTML 中的元素(如 img)和 HTML 中的文本片段(如 “Scroll down for more details”)沒有多少明顯的相似之處。但這是因爲您考慮的可能是每種類型的功能,關注的是它們的不同點。

但是如果從另一個角度觀察,DOM 樹中的每個元素和每段文本都有一個父親,這個父節點可能是另一個元素(比如嵌套在 p 元素中的 img)的孩子,或者 DOM 樹中的頂層元素(這是每個文檔中都出現一次的特殊情況,即使用 html 元素的地方)。另外,元素和文本都有一個類型。顯然,元素的類型就是元素,文本的類型就是文本。每個節點還有某種定義明確的結構:下面還有節點(如子元素)嗎?有兄弟節點(與元素或文本 “相鄰的” 節點)嗎?每個節點屬於哪個文檔?

顯然,大部分內容聽起來很抽象。實際上,說一個元素的類型是元素似乎有點冒傻氣。但是要真正認識到將節點作爲通用對象類型的價值,必須抽象一點來思考。

通用節點類型

DOM 代碼中最常用的任務就是在頁面的 DOM 樹中導航。比方說,可以通過其 “id” 屬性定位一個 form,然後開始處理那個 form 中內嵌的元素和文本。其中可能包含文字說明、輸入字段的標籤、真正的 input 元素,以及其他 HTML 元素(如 img)和鏈接(a 元素)。如果元素和文本是完全不同的類型,就必須爲每種類型編寫完全不同的代碼。

如果使用一種通用節點類型情況就不同了。這時候只需要從一個節點移動到另一個節點,只有當需要對元素或文本作某種特殊處理時才需要考慮節點的類型。如果僅僅在 DOM 樹中移動,就可以與其他節點類型一樣用同樣的操作移動到元素的父節點或者子節點。只有當需要某種節點類型的特殊性質時,如元素的屬性,才需要對節點類型作專門處理。將 DOM 樹中的所有對象都看作節點可以簡化操作。記住這一點之後,接下來我們將具體看看 DOM 節點構造應該提供什麼,首先從屬性和方法開始。





回頁首


節點的屬性

使用 DOM 節點時需要一些屬性和方法,因此我們首先來討論節點的屬性和方法。DOM 節點的屬性主要有:

  • nodeName 報告節點的名稱(詳見下述)。
  • nodeValue 提供節點的 “值”(詳見後述)。
  • parentNode 返回節點的父節點。記住,每個元素、屬性和文本都有一個父節點。
  • childNodes 是節點的孩子節點列表。對於 HTML,該列表僅對元素有意義,文本節點和屬性節點都沒有孩子。
  • firstChild 僅僅是 childNodes 列表中第一個節點的快捷方式。
  • lastChild 是另一種快捷方式,表示 childNodes 列表中的最後一個節點。
  • previousSibling 返回當前節點之前 的節點。換句話說,它返回當前節點的父節點的 childNodes 列表中位於該節點前面的那個節點(如果感到迷惑,重新讀前面一句)。
  • nextSibling 類似於 previousSibling 屬性,返回父節點的 childNodes 列表中的下一個節點。
  • attributes 僅用於元素節點,返回元素的屬性列表。

其他少數幾種屬性實際上僅用於更一般的 XML 文檔,在處理基於 HTML 的網頁時沒有多少用處。

不常用的屬性

上述大部分屬性的意義都很明確,除了 nodeNamenodeValue 屬性以外。我們不是簡單地解釋這兩個屬性,而是提出兩個奇怪的問題:文本節點的 nodeName 應該是什麼?類似地,元素的 nodeValue 應該是什麼

如果這些問題難住了您,那麼您就已經瞭解了這些屬性固有的含糊性。nodeNamenodeValue 實際上並非適用於所有 節點類型(節點的其他少數幾個屬性也是如此)。這就說明了一個重要概念:任何這些屬性都可能返回空值(有時候在 JavaScript 中稱爲 “未定義”)。比方說,文本節點的 nodeName 屬性是空值(或者在一些瀏覽器中稱爲 “未定義”),因爲文本節點沒有名稱。如您所料,nodeValue 返回節點的文本。

類似地,元素有 nodeName,即元素名,但元素的 nodeValue 屬性值總是空。屬性同時具有 nodeNamenodeValue。下一節我還將討論這些單獨的類型,但是因爲這些屬性是每個節點的一部分,因此在這裏有必要提一提。

現在看看 清單 1,它用到了一些節點屬性。


清單 1. 使用 DOM 中的節點屬性
    // 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.
    }





回頁首


節點方法

接下來看看所有節點都具有的方法(與節點屬性一樣,我省略了實際上不適用於多數 HTML DOM 操作的少數方法):

  • insertBefore(newChild, referenceNode)newChild 節點插入到 referenceNode 之前。記住,應該對 newChild 的目標父節點調用該方法。
  • replaceChild(newChild, oldChild)newChild 節點替換 oldChild 節點。
  • removeChild(oldChild) 從運行該方法的節點中刪除 oldChild 節點。
  • appendChild(newChild)newChild 添加到運行該函數的節點之中。newChild 被添加到目標節點孩子列表中的末端
  • hasChildNodes() 在調用該方法的節點有孩子時則返回 true,否則返回 false。
  • hasAttributes() 在調用該方法的節點有屬性時則返回 true,否則返回 false。

注意,大部分情況下所有這些方法處理的都是節點的孩子。這是它們的主要用途。如果僅僅想獲取文本節點值或者元素名,則不需要調用這些方法,使用節點屬性就可以了。清單 2清單 1 的基礎上增加了方法使用。


清單 2. 使用 DOM 中的節點方法
// 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);
          }
        }
      }
    }

測試一下!

目前雖然只看到了兩個例子,清單 12,不過通過這兩個例子您應該能夠了解使用 DOM 樹能夠做什麼。如果要嘗試一下這些代碼,只需要將 清單 3 拖入一個 HTML 文件並保存,然後用 Web 瀏覽器打開。


清單 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>

將該頁面加載到瀏覽器後,可以看到類似 圖 1 所示的畫面。


圖 1. 用按鈕運行 JavaScript 的 HTML 頁面
用按鈕運行 JavaScript 的 HTML 頁面

單擊 Test me! 將看到 圖 2 所示的警告框。


圖 2. 使用 nodeValue 顯示元素名的警告框
使用 nodeValue 顯示元素名的警告框

代碼運行完成後,圖片將從頁面中實時刪除,如 圖 3 所示。


圖 3. 使用 JavaScript 實時刪除圖像
使用 JavaScript 實時刪除圖像




回頁首


API 設計問題

再看一看各種節點提供的屬性和方法。對於那些熟悉面向對象(OO)編程的人來說,它們說明了 DOM 的一個重要特點:DOM 並非完全面向對象的 API。首先,很多情況下要直接使用對象的屬性而不是調用節點對象的方法。比方說,沒有 getNodeName() 方法,而要直接使用 nodeName 屬性。因此節點對象(以及其他 DOM 對象)通過屬性而不是函數公開了大量數據。

其次,如果習慣於使用重載對象和麪向對象的 API,特別是 Java 和 C++ 這樣的語言,就會發現 DOM 中的對象和方法命名有點奇怪。DOM 必須能用於 C、Java 和 JavaScript(這只是其中的幾種語言),因此 API 設計作了一些折衷。比如,NamedNodeMap 方法有兩種不同的形式:

  • getNamedItem(String name)
  • getNamedItemNS(Node node)

對於 OO 程序員來說這看起來非常奇怪。兩個方法目的相同,只不過一個使用 String 參數而另一個使用 Node 參數。多數 OO API 中對這兩種版本都會使用相同的方法名。運行代碼的虛擬機將根據傳遞給方法的對象類型決定運行哪個方法。

問題在於 JavaScript 不支持這種稱爲方法重載 的技術。換句話說,JavaScript 要求每個方法或函數使用不同的名稱。因此,如果有了一個名爲 getNamedItem() 的接受字符串參數的方法,就不能再有另一個方法或函數也命名爲 getNamedItem(),即使這個方法的參數類型不同(或者完全不同的一組參數)。如果這樣做,JavaScript 將報告錯誤,代碼不會按照預期的方式執行。

從根本上說,DOM 有意識地避開了方法重載和其他 OO 編程技術。這是爲了保證該 API 能夠用於多種語言,包括那些不支持 OO 編程技術的語言。後果不過是要求您多記住一些方法名而已。好處是可以在任何語言中學習 DOM,比如 Java,並清楚同樣的方法名和編碼結構也能用於具有 DOM 實現的其他語言,如 JavaScript。

讓程序員小心謹慎

如果深入研究 API 設計或者僅僅非常關注 API 設計,您可能會問:“爲何節點類型的屬性不能適用於所有節點?” 這是一個很好的問題,答案和政治以及決策制定而非技術原因關係更密切。簡單地說,答案就是,“誰知道!但有點令人惱火,不是嗎?”

屬性 nodeName 意味着允許每種類型的節點都有一個名字,但是很多情況下名字要麼未定義,要麼是對於程序員沒有意義的內部名(比如在 Java 中,很多情況下文本節點的 nodeName 被報告爲 “#text”)。從根本上說,必須假設您得自己來處理錯誤。直接訪問 myNode.nodeName 然後使用該值是危險的,很多情況下這個值爲空。因此與通常的編程一樣,程序員要謹慎從事。





回頁首


通用節點類型

現在已經介紹了 DOM 節點的一些特性和屬性(以及一些奇特的地方),下面開始講述您將用到的一些特殊節點類型。多數 Web 應用程序中只用到四種節點類型:

  • 文檔節點表示整個 HTML 文檔。
  • 元素節點表示 HTML 元素,如 aimg
  • 屬性節點表示 HTML 元素的屬性,如 hrefa 元素)或 srcimg 元素)。
  • 文本節點表示 HTML 文檔中的文本,如 “Click on the link below for a complete set list”。這是出現在 pah2 這些元素中的文字。

處理 HTML 時,95% 的時間是跟這些節點類型打交道。因此本文的其餘部分將詳細討論這些節點。(將來討論 XML 的時候將介紹其他一些節點類型。)





回頁首


文檔節點

基本上所有基於 DOM 的代碼中都要用到的第一個節點類型是文檔節點。文檔節點 實際上並不是 HTML(或 XML)頁面中的一個元素而是頁面本身。因此在 HTML Web 頁面中,文檔節點就是整個 DOM 樹。在 JavaScript 中,可以使用關鍵字 document 訪問文檔節點:

// 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 關鍵字返回當前網頁的 DOM 樹。從這裏可以開始處理樹中的所有節點。

也可使用 document 對象創建新節點,如下所示:

  • createElement(elementName) 使用給定的名稱創建一個元素。
  • createTextNode(text) 使用提供的文本創建一個新的文本節點。
  • createAttribute(attributeName) 用提供的名稱創建一個新屬性。

這裏的關鍵在於這些方法創建節點,但是並沒有將其附加或者插入到特定的文檔中。因此,必須使用前面所述的方法如 insertBefore()appendChild() 來完成這一步。因此,可使用下面的代碼創建新元素並將其添加到文檔中:

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 頁面 DOM 樹的訪問,就可以直接使用元素、屬性和文本了。





回頁首


元素節點

雖然會大量使用元素節點,但很多需要對元素執行的操作都是所有節點共有的方法和屬性,而不是元素特有的方法和屬性。元素只有兩組專有的方法:

  1. 與屬性處理有關的方法
    • getAttribute(name) 返回名爲 name 的屬性值。
    • removeAttribute(name) 刪除名爲 name 的屬性。
    • setAttribute(name, value) 創建一個名爲 name 的屬性並將其值設爲 value
    • getAttributeNode(name) 返回名爲 name 的屬性節點(屬性節點在 下一節 介紹)。
    • removeAttributeNode(node) 刪除與指定節點匹配的屬性節點。
  2. 與查找嵌套元素有關的方法
    • getElementsByTagName(elementName) 返回具有指定名稱的元素節點列表。

這些方法意義都很清楚,但還是來看幾個例子吧。

處理屬性

處理元素很簡單,比如可用 document 對象和上述方法創建一個新的 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);

現在看起來應該非常簡單了。實際上,只要理解了節點的概念並知道有哪些方法可用,就會發現在 Web 頁面和 JavaScript 代碼中處理 DOM 非常簡單。在上述代碼中,JavaScript 創建了一個新的 img 元素,設置了一些屬性然後添加到 HTML 頁面的 body 元素中。

查找嵌套元素

發現嵌套的元素很容易。比如,下面的代碼用於發現和刪除 清單 3 所示 HTML 頁面中的所有 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() 完成類似的功能:

      
// 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);
      }





回頁首


屬性節點

DOM 將屬性表示成節點,可以通過元素的 attributes 來訪問元素的屬性,如下所示:

      
// 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 來說屬性有一些特殊的地方。一方面,屬性實際上並不像其他元素或文本那樣是元素的孩子,換句話說,屬性並不出現在元素 “之下”。同時,屬性顯然和元素有一定的關係,元素 “擁有” 屬性。DOM 使用節點表示屬性,並允許通過元素的專門列表來訪問屬性。因此屬性是 DOM 樹的一部分,但通常不出現在樹中。有理由說,屬性和 DOM 樹結構其他部分之間的關係有點模糊。

需要指出的是,attributes 屬性實際上是對節點類型而非侷限於元素類型來說的。有點古怪,不影響您編寫代碼,但是仍然有必要知道這一點。

雖然也能使用屬性節點,但通常使用元素類的方法處理屬性更簡單。其中包括:

  • getAttribute(name) 返回名爲 name 的屬性值。
  • removeAttribute(name) 刪除名爲 name 的屬性。
  • setAttribute(name, value) 創建一個名爲 name 的屬性並將其值設爲 value

這三個方法不需要直接處理屬性節點。但允許使用簡單的字符串屬性設置和刪除屬性及其值。





回頁首


文本節點

需要考慮的最後一種節點是文本節點(至少在處理 HTML DOM 樹的時候如此)。基本上通常用於處理文本節點的所有屬性都屬於節點對象。實際上,一般使用 nodeValue 屬性來訪問文本節點的文本,如下所示:

var pElements = bodyElement.getElementsByTagName("p");
for (i=0; i<pElements.length; i++) {
  var pElement = pElements.item(i);
  var text = pElement.firstChild.nodeValue;
  alert(text);
}

少數其他幾種方法是專門用於文本節點的。這些方法用於增加或分解節點中的數據:

  • appendData(text) 將提供的文本追加到文本節點的已有內容之後。
  • insertData(position, text) 允許在文本節點的中間插入數據。在指定的位置插入提供的文本。
  • replaceData(position, length, text) 從指定位置開始刪除指定長度的字符,用提供的文本代替刪除的文本。




回頁首


什麼節點類型?

到目前爲止看到的多數代碼都假設已經知道處理的節點是什麼類型,但情況並非總是如此。比方說,如果在 DOM 樹中導航並處理一般的節點類型,可能就不知道您遇到了元素還是文本。也許獲得了 p 元素的所有孩子,但是不能確定處理的是文本、b 元素還是 img 元素。這種情況下,在進一步的處理之前需要確定是什麼類型的節點。

所幸的是很容易就能做到。DOM 節點類型定義了一些常量,比如:

  1. Node.ELEMENT_NODE 是表示元素節點類型的常量。
  2. Node.ATTRIBUTE_NODE 是表示屬性節點類型的常量。
  3. Node.TEXT_NODE 是表示文本節點類型的常量。
  4. Node.DOCUMENT_NODE 是表示文檔節點類型的常量。

還有其他一些節點類型,但是對於 HTML 除了這四種以外很少用到。我有意沒有給出這些常量的值,雖然 DOM 規範中定義了這些值,永遠不要直接使用那些值,因爲這正是常量的目的!

nodeType 屬性

可使用 nodeType 屬性比較節點和上述常量 —— 該屬性定義在 DOM node 類型上因此可用於所有節點,如下所示:

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 + "'");
}

這個例子非常簡單,但說明了一個大問題:得到節點的類型非常 簡單。更有挑戰性的是知道節點的類型之後確定能做什麼,只要掌握了節點、文本、屬性和元素類型提供了什麼屬性和方法,就可以自己進行 DOM 編程了。

好了,快結束了。

實踐中的挫折

nodeType 屬性似乎是使用節點的一個入場卷 —— 允許確定要處理的節點類型然後編寫處理該節點的代碼。問題在於上述 Node 常量定義不能正確地用於 Internet Explorer。因此如果在代碼中使用 Node.ELEMENT_NODENode.TEXT_NODE 或其他任何常量,Internet Explorer 都將返回如 圖 4 所示的錯誤。


圖 4. Internet Explorer 報告錯誤
Internet Explorer 不支持 Node 構造

任何時候在 JavaScript 中使用 Node 常量,Internet Explorer 都會報錯。因爲多數人仍然在使用 Internet Explorer,應該避免在代碼中使用 Node.ELEMENT_NODENode.TEXT_NODE 這樣的構造。儘管據說即將發佈的新版本 Internet Explorer 7.0 將解決這個問題,但是在 Internet Explorer 6.x 退出舞臺之前仍然要很多年。因此應避免使用 Node,要想讓您的 DOM 代碼(和 Ajax 應用程序)能用於所有主要瀏覽器,這一點很重要。





回頁首


結束語

準備成爲頂尖的網頁設計師嗎?

如果您準備瞭解甚至掌握 DOM,您就會成爲最頂尖的 Web 編程人員。多數 Web 程序員知道如何使用 JavaScript 編寫圖像滾動或者從表單中提取值,有些甚至能夠向服務器發送請求和接收響應(閱讀本系列的前幾篇文章之後您也能做到)。但膽小鬼或者沒有經驗的人不可能做到即時修改網頁結構。

在本系列的上幾期文章中您已經學習了很多。現在,您 應該再坐等下一篇文章期待我介紹各種聰明的 DOM 樹用法。現在的家庭作業是看看如何使用 DOM 創造出富有想像力的效果或者漂亮的界面。利用近幾期文章中所學的知識開始實驗和練習。看看能否建立感覺更與桌面應用程序接近的網站,對象能夠響應用戶的動作在屏幕上移動。

最好在屏幕上爲每個對象畫一個邊界,這樣就能看到 DOM 樹中的對象在何處,然後再移動對象。創建節點並將其添加到已有的孩子列表中,刪除沒有嵌套節點的空節點,改變節點的 CSS 樣式,看看孩子節點是否會繼承這些修改。可能性是無限的,每當嘗試一些新東西時,就學到了一些新的知識。盡情地修改您的網頁吧!

在 DOM 三部曲的最後一期文章中,我 介紹如何把一些非常棒的有趣的 DOM 應用結合到編程中。我將不再是從概念上說教和解釋 API,而會提供一些代碼。在此之前先發揮您自己的聰明才智,看看能做些什麼。

來自  http://www.ibm.com/developerworks/cn/xml/wa-ajaxintro5/index.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章