天天看點

JavaScript 和文檔對象模型(DOM)

2008 年 7 月 11 日 更新

雖然很多人都熟知文檔對象模型(DOM)是處理 XML的基礎,但使用 HTML 的浏覽器開始使這個主題發生一些變化。随着更新的浏覽器通過用戶端腳本(比如 JavaScript)實作 W3C文檔對象模型,現在 DOM 又經曆了一個輪回。本文将介紹通過 JavaScript 實作 DOM,并描述 Web頁面的建構,使用者可以向這個頁面添加并編輯注釋。

注意:本文使用相容 DOM 的浏覽器,比如 Netscape 6.x、Internet Explorer 6 或 Mozilla 1.0。熟悉 Java 技術或其他語言的文檔對象模型會有所幫助,但不是必要條件。不過您應該大體熟悉 JavaScript。

為什麼使用 DOM 而不使用 DHTML?

在本文中,我從文檔對象模型的角度分析 Web頁面的結構:檢查子節點和父節點,以及向現有文檔添加并編輯節點。為了示範這個過程,我将建構一個頁面。使用者可以在這個頁面上任意建立注釋并編輯這些注釋的内容(這個頁面不會很複雜;我将把一些任務留給您,比如儲存和移動内容以及重新調整内容的大小等)。既然使用 DHTML技術就可以輕松實作這些操作,為什麼還要使用 DOM 呢?

開發 XML 的其中一股推動力是将 HTML 片段化,而 DHTML 順應了這一潮流。雖然某些方面已經形成标準,但浏覽器如何處理不同的方面仍然存在很大的分歧。DOM 是可以在整個領域範圍内實作的标準接口集,是以程式設計人員隻需編寫一個版本的頁面。

另外,Web 頁面不斷朝 XML 的方向發展。XHTML 1.0 隻是由 HTML 4.0.1 改編而來的 XML,并且也越來越多地使用 XML 生成 Web 頁面。最終,DOM 将是在這些頁面内通路元素和文本的主要技術(也可将它用于 XSLT 樣式表)。

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

基礎頁面

讓我們從基礎的頁面開始,該頁面包含一個工作區域和表單,它顯示了已建立注釋的數量:

清單 1. 基礎頁面

<html>
  <head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

  </head>
  <body>
    <div id="mainDiv" style="height:95%; width:95%; border:3px solid red; 
                                                           padding: 10px;">
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes:
                       <input type="text" name="total" value="0" size="3"/>
           <input type="button" value="Add a new note"/>
       </form>

    </div>
  </body>
</html>      

清單 1 展示了帶有幾種樣式(隻是為了使頁面好看些)的基礎頁面。頁面的正文包括一個單個的

div

元素,該元素包含一個 heading 元素(

h1

)和一個

form

元素。即使是在沒有使用 DOM 的浏覽器裡,也可以在表單内通路資訊。

圖 1. 基礎頁面

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

使用 DHTML

使用 DHTML 可以通路甚至修改表單字段的資訊。例如,您可以建立一個腳本将目前表單的值增加 1,然後告訴頁面在使用者點選按鈕時執行這個腳本:

清單 2. 增加目前的數值

<html><head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

    <script type="text/javascript">
      function incrementCurrent() {
        current = parseInt(document.forms["noteForm"].total.value);
        document.forms["noteForm"].total.value = current + 1;
      }
    </script>

  </head><body>
    <div id="mainDiv" style="height:95%; width:95%; border:3px solid red; 
                                                           padding: 10px;">
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes:
                      <input type="text" name="total" value="0" size="3"/>
           <input type="button" value="Add a new note" 
                                             οnclick="incrementCurrent()"/>
       </form>

    </div>
</body></html>      

在 清單 2 中,

incrementCurrent()

函數接受文檔對象,然後将

noteForm

對象從頁面内的表單數組中抽出。該函數從

noteForm

對象獲得表單字段

total

并檢索值,然後在頁面内更新這個值。

這種更改是即時的。更改頁面、重新加載并反複按按鈕,您将發現文本字段每一次都會相應地進行更新。

同樣,您也可以使用 DHTML 屬性檢索

div

元素的文本。由于它有一個

mainDiv

id

,是以可以使用

innerHTML

屬性,如:

theText = mainDiv.innerHTML;

在這個例子中,使用了兩個 DHTML 技術:

forms

數組,以及通過基于屬性值的名稱調用元素(而不是通過元素名或總體結構)。但存在的問題是這種方法不夠通用。雖然可以對表單的名稱使用一個變量,如果碰到不使用表單的呈現,應該怎麼辦呢?

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

DOM 和 JavaScript

如果存在一個定義文檔對象模型的原則,它将把資訊排列成父-子層次結構。例如,參看以下的 XML:

<parentElement><childElement>My Text Node</childElement></parentElement>

有 3 個節點:root 節點是

parentElement

。它有一個子元素

childElement

childElement

元素有自己的父元素

parentElement

,作為子元素的文本節點的值是 “My Text Node”。這個文本節點的父元素是

childElement

。共享一個父元素的兩個節點稱為兄弟(sibling)節點。注意,文本就是它本身的節點。元素實際上并沒有值,它們僅有文本子節點。

您應該比較熟悉如何使用 Java 技術或其他語言讀取 XML 文檔的結構。實施這個操作要用到一個帶有定義良好的函數和對象屬性的 API。例如,這個文檔有一個作為其根元素的

html

元素,該元素有兩個子元素

head

body

(為簡單起見,我删除了正常情況下會出現在這些元素之間的空格;至于如何處理這些空格,不同的浏覽器之間還沒有達成一緻)。要通過 Java 技術通路

body

元素,您可以使用幾個不同的表達式,假如您已經命名了

Document

對象

document

第一個步驟是獲得所有元素的子元素的清單,然後從該清單選擇一個特定的條目,就像以下的例子一樣:

bodyElement = document.getChildNodes().item(0).getChildNodes().item(1);

在這裡,首先獲得

html

元素(該元素是文檔的第一個子元素),然後獲得它的第二個子元素

body

getChildNodes()

是基于零的)。另一種辦法是将

html

元素作為第一個子元素直接通路,然後移動到它的第一個子元素(

head

),最後移動到該元素的下一個兄弟元素(第二個子元素

body

):

bodyElement = document.getFirstChild().getFirstChild().getNextSibling();

從這裡您可以獲得節點的類型:

typeInt = bodyElement.getNodeType()

節點類型以整數傳回,并且允許您适當地處理每一個節點;一個元素(類型

1

)有名稱沒有值,但是一個文本節點(類型

3

)有值沒有名稱。

了解了這些後,就可以擷取元素的名稱:

elementName = bodyElement.getNodeName();

或它的文本内容:

elementContent = bodyElement.getFirstChild().getNodeValue();

(記住,元素的文本本身就是一個節點,而該元素則是它的父節點)。

當将這些函數移動到 JavaScript 時,相同的基本 API 已經準備就緒,但處理方式有些不同。例如,可以直接通路屬性,而不是通過

get

set

方法通路,是以 JavaScript 中的相同語句會表示為:

bodyElement = document.childNodes.item(0).childNodes.item(1); bodyElement = document.firstChild.firstChild.nextSibling;

您可以從這裡獲得元素的類型和名稱及其内容:

ElementType = bodyElement.nodeType; elementName = bodyElement.nodeName; elementContent = bodyElement.firstChild.nodeValue;

但是需要注意,隻能更改屬性的名稱。傳回對象的函數保持不變。例如,可以根據 ID 檢索某個特定的對象,就像下面的示例一樣:

formElement = document.getElementById("noteForm");

這隻是基本原理,現在我們看看它的實際運作情況。

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

添加新的注釋

實際節點本身隻是一個帶有标準文本和連結的框,這個框允許使用者稍後再編輯文本,如 圖 2 所示。

圖 2. 新的節點

JavaScript 和文檔對象模型(DOM)

這個新節點與下面的 HTML 等效:

清單 3. 目标 HTML

<div id="note1" style="width: 100; height:100; border: 1px solid blue; 
                       background-color: yellow; position: absolute; 
                       top: 150; left: 135">
    <a href="javascript:editNote('note1')" target="_blank" rel="external nofollow" >edit</a>
    <br />
    New note
</div>      

使用标準的 DOM 添加實際元素,如 清單 4 所示:

清單 4. 添加新的注釋元素

<html>
  <head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

    <script type="text/javascript">
...
      function getCurrentNumber() {
        formElement = document.getElementById("noteForm");
        return formElement.childNodes.item(1).value;
      }

      function makeNewNote(){
        mainDivElement = document.getElementById("mainDiv");

        newNote = document.createElement("div");
        newNote.setAttribute("id", "note"+getCurrentNumber());

        mainDivElement.appendChild(newNote);

        incrementCurrent();

      }
    </script>
  </head>
  <body>
    <div id="mainDiv" style="height:85%; width:85%; border:3px solid red; 
                                             padding: 10px; z-index: -100" >
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes  <input type="text" name="total" value="0" 
                                             size="3"/>
           <input type="button" value="Add a new note" 
                                             οnclick="makeNewNote()"/>
       </form>

    </div>
  </body>
</html>      

在這個示例中,更改了按鈕進而執行

makeNewNote()

而不是

incrementCurrent()

,盡管

makeNewNote()

還使用該函數。首先,通過

getElementById()

獲得針對主元素

div

的引用,這個元素最終會包含注釋。然後可以使用

document

對象建立一個名為

div

的新元素,就像在其他語言中一樣。要設定

id

屬性,您隻需要在新的元素上使用

setAttribute()

方法。

每一個注釋都有一個獨特的

id

屬性,是以您需要知道目前的總和是多少。要獲得這個資訊,必須從 form 元素本身開始。可以從這裡擷取子元素清單。第一個(帶有一個索引

)是文本,第二個是(帶有一個索引

1

)是實際的

input

元素。

.value

又是什麼呢?是不是輸入錯誤?它應該是

nodeValue

,是嗎?

實際上不是。記住,元素沒有值。乍一看,好像我将 DOM 和 DHTML 屬性混在一起,但實際上現在擷取的元素不僅是

org.w3c.dom.Element

的實作,還是

org.w3c.dom.html.HTMLInputElement

(它也包含了一個代表表單字段值的值屬性)的實作。在這一方面,DOM 模仿了一些(雖然不是全部)通過 DHTML 可以實作的屬性。

設定了屬性之後,隻需要将新的

div

元素附加到

mainDiv

元素,

div

元素将出現在這個位置上。或者,如果它有任何顯示屬性或文本的話,它至少會出現在這個位置上。

要添加樣式資訊,将需要實際使用 DHTML 樣式對象:

清單 5. 添加樣式資訊

...
      function makeNewNote(){
        mainDivElement = document.getElementById("mainDiv");

        newNote = document.createElement("div");
        newNote.setAttribute("id", "note"+getCurrentNumber());

        newNote.style.width="100";
        newNote.style.height="100";
        newNote.style.;
        newNote.style.backgroundColor="yellow";
        newNote.style.position="absolute";
        newNote.style.top=(150);
        newNote.style.left=(25 + 110*getCurrentNumber());

        mainDivElement.appendChild(newNote);

        incrementCurrent();

      }
...      

添加的結果是産生一個藍邊的黃色小框,如 圖 3 所示。

圖 3. 空的框

JavaScript 和文檔對象模型(DOM)

注意,注釋的

left

屬性取決于目前注釋的數量,它随着每個注釋的添加而增長。您可以通過這種方式添加一系列框,如 圖 3 所示。

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

添加内容

添加

div

的内容會呈現一個小問題。我可以使用 innerHTML 屬性:

newNote.innerHTML = "<a href=/"javascript:editNote('note"  
                     +getCurrentNumber()+")/">edit</a><br />New note";      

但我如何直接使用 DOM 方法來實作?首先想到的可能是簡單地設定

div

元素的文本子節點的值:

noteText = document.createTextNode(
          "<a href=/"javascript:editNote('note"+getCurrentNumber()+")/">"+
          "edit</a><br />New note");
newNote.appendChild(noteText);      

雖然文本真的得到了添加,但結果可能與預期不同,如 圖 4 所示。

圖 4. 框中的文本

JavaScript 和文檔對象模型(DOM)

問題是您并沒有真的添加文本,而是混合了内容,包括文本和元素。浏覽器認為您将這看作是 CDATA(完全按照字面意義得出),并且也沒有建立元素。不是簡單地在一個塊中添加所有的内容,您必須實際添加每個元素:

清單 6. 添加内容

...
  function makeNewNote(){
    mainDivElement = document.getElementById("mainDiv");

    newNote = document.createElement("div");
    newNote.setAttribute("id", "note"+getCurrentNumber());
...

      editLink = getEditLink("note"+getCurrentNumber());
      newNote.appendChild(editLink);
      newNote.appendChild(document.createElement("br"));
 
      noteText = document.createTextNode("New Form");
      newNote.appendChild(noteText);

    mainDivElement.appendChild(newNote);

    incrementCurrent();

  }

    function getEditLink(thisId){
      editLink = document.createElement("a");
      linkText = document.createTextNode("edit");

      editLink.setAttribute("href", "javascript:editNote('"+thisId+"')");
        
      editLink.appendChild(linkText);
      return editLink;
    }
...      

首先,您已經建立了一個傳回一個對象的新函數

getEditLink

。這個對象是用标準 DOM 方法建立的

a

元素。接下來,添加一個标準的 break 标記

br

,最後添加包含實際節點文本的節點。

結果是得到完成的節點,而元素保持完整并可使用。

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

更改現有的節點

現在有了内容,但怎樣才能更改它呢?因為您單個地添加元素,是以隻能編輯代表節點文本的單個文本節點。這可以通過 3 種方式來實作。首先删除有問題的節點,然後添加一個新的節點:

清單 7. 删除節點并添加一個新的節點

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        oldNode = theDiv.firstChild.nextSibling.nextSibling;
        theDiv.removeChild(oldNode);

        newNode = document.createTextNode(newText);
        theDiv.appendChild(newNode);

      }
...      

另一種方法很簡單,隻需代換現有的節點:

清單 8. 替換一個節點

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        oldNode = theDiv.firstChild.nextSibling.nextSibling;
        newNode = document.createTextNode(newText);
        theDiv.replaceChild(newNode, oldNode);
      }
...      

在這個示例中,用

newNode

代替了

oldNode

,同時也相應地更改了文檔。

最後一個辦法是更改現有節點的文本:

清單 9. 更改現有的節點

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        theDiv.firstChild.nextSibling.nextSibling.nodeValue=newText;
      }
...      

因為

editNote

使用相應的

div

id

值作為參數,相同的函數可以用于任意節點,如 圖 5 所示。

圖 5. 最後的頁面

JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
JavaScript 和文檔對象模型(DOM)
回頁首

結束語

在本文,您已經了解在 Web 頁面的 JavaScript 中使用 DOM 的基礎知識。您可以在使用 JavaScript 的地方(比如在 XSLT 樣式表中)使用這些基本原理。

文檔對象模型将 XML 文檔内的元素、文本和其他類型的節點呈現為一系列的父子關系。操作這些單獨的節點可以影響頁面本身。除了 DOM Core方法以外,XHTML 頁面也可以公開作為 DOM HTML 子產品的一部分的屬性和方法,DOM HTML 子產品試圖整合許多程式設計人員用了好幾年的DHTML 屬性。

繼續閱讀