前端重新學習(16)DOM基礎

JavaScript由三部分組成 ECMAScript+DOM+BOM組成

DOM的學習分爲三塊,1.DOM基礎   2.DOM進階( Selectors API和的 Element Traversal (元素遍歷)規範,)3.DOM、DOM2DOM3的改進


DOM(文檔對象模型)是針對 HTML和 XML文檔的一個 API(應用程序編程接口)。DOM描 繪了一個層次化的節點樹,允許開發人員添加、移除和修改頁面的某一部分。DOM 脫胎於 Netscape及微軟公司創始的 DHTML(動態 HTML),但現在它已經成爲表現和操作頁面標記的真正的跨 平臺、語言中立的方式

1998年 10月 DOM1級規範成爲W3C的推薦標準,爲基本的文檔結構及查詢提供了接口。本文主 要討論與瀏覽器中的 HTML頁面相關的 DOM1級的特性和應用,以及 JavaScript對 DOM1級的實現。 IE、Firefox、Safari、Chrome和 Opera都非常完善地實現了 DOM

注意,IE中的所有 DOM對象都是以 COM對象的形式實現的。這意味着 IE中的 DOM對象與原生 JavaScript對象的行爲或活動特點並不一致。本章將較多地談及這些 差異。 

DOM是語言中立的 API,用於訪問和操作 HTML和 XML文檔。DOM1級將 HTML和 XML文檔 形象地看作一個層次化的節點樹,可以使用 JavaScript 來操作這個節點樹,進而改變底層文檔的外觀和 結構。 DOM由各種節點構成,簡要總結如下。

  • 基本的節點類型是 Node,用於抽象地表示文檔中一個獨立的部分;所有其他類型都繼承自 Node。
  •  Document 類型表示整個文檔,是一組分層節點的根節點。在 JavaScript中,document 對象是 Document 的一個實例。使用 document 對象,有很多種方式可以查詢和取得節點。
  •  Element 節點表示文檔中的所有 HTML或 XML元素,可以用來操作這些元素的內容和特性。
  • 另外還有一些節點類型,分別表示文本內容、註釋、文檔類型、CDATA區域和文檔片段。 

 

訪問 DOM 的操作在多數情況下都很直觀,不過在處理<script>和<style>元素時還是存在一些 複雜性。由於這兩個元素分別包含腳本和樣式信息,因此瀏覽器通常會將它們與其他元素區別對待。這 些區別導致了在針對這些元素使用 innerHTML 時,以及在創建新元素時的一些問題。 理解 DOM的關鍵,就是理解 DOM對性能的影響。DOM操作往往是 JavaScript程序中開銷大的 部分,而因訪問 NodeList 導致的問題爲多。NodeList 對象都是“動態的”,這就意味着每次訪問 NodeList 對象,都會運行一次查詢。有鑑於此,好的辦法就是儘量減少 DOM操作。 

獲取DOM與DOM2、DOM3區別以及DOM2新特性 

獲取HTML DOM 核心(Core) DOM區別與聯繫

節點層次

節點分爲幾種不同的類 型,每種類型分別表示文檔中不同的信息及(或)標記。每個節點都擁有各自的特點、數據和方法,另 外也與其他節點存在某種關係。節點之間的關係構成了層次,而所有頁面標記則表現爲一個以特定節點 爲根節點的樹形結構。以下面的 HTML爲例:

 

 

Node類型 

DOM1 級定義了一個 Node 接口,該接口將由 DOM 中的所有節點類型實現。這個 Node 接口在 JavaScript 中是作爲 Node 類型實現的;除了 IE 之外,在其他所有瀏覽器中都可以訪問到這個類型。 JavaScript中的所有節點類型都繼承自 Node 類型,因此所有節點類型都共享着相同的基本屬性和方法。
 每個節點都有一個 nodeType 屬性,用於表明節點的類型。節點類型由在 Node 類型中定義的下列 12個數值常量來表示,任何節點類型必居其一: 

  • Node.ELEMENT_NODE(1);
  • Node.ATTRIBUTE_NODE(2);
  • Node.TEXT_NODE(3);
  • Node.CDATA_SECTION_NODE(4);
  • Node.ENTITY_REFERENCE_NODE(5);
  • Node.ENTITY_NODE(6);
  • Node.PROCESSING_INSTRUCTION_NODE(7);
  • Node.COMMENT_NODE(8);
  •  Node.DOCUMENT_NODE(9);
  • Node.DOCUMENT_TYPE_NODE(10);
  • Node.DOCUMENT_FRAGMENT_NODE(11);
  • Node.NOTATION_NODE(12)。 

通過比較上面這些常量,可以很容易地確定節點的類型,例如: 

if (someNode.nodeType == Node.ELEMENT_NODE){
   //在 IE中無效
     alert("Node is an element.");
 } 
 

這個例子比較了 someNode.nodeType 與 Node.ELEMENT_NODE 常量。如果二者相等,則意味着 someNode 確實是一個元素。然而,由於 IE沒有公開 Node 類型的構造函數,因此上面的代碼在 IE中 會導致錯誤。爲了確保跨瀏覽器兼容,好還是將 nodeType 屬性與數字值進行比較,如下所示: 

if (someNode.nodeType == 1){
    //適用於所有瀏覽器
     alert("Node is an element.");
 }

並不是所有節點類型都受到 Web 瀏覽器的支持。開發人員常用的就是元素和文本節點。本章後 面將詳細討論每個節點類型的受支持情況及使用方法。 

1. nodeName 和 nodeValue 屬性 

 要了解節點的具體信息,可以使用 nodeName 和 nodeValue 這兩個屬性。這兩個屬性的值完全取 決於節點的類型。在使用這兩個值以前,好是像下面這樣先檢測一下節點的類型。 

if (someNode.nodeType == 1){
     value = someNode.nodeName;
    //nodeName 的值是元素的標籤名
 } 
 

在這個例子中,首先檢查節點類型,看它是不是一個元素。如果是,則取得並保存 nodeName 的值。 對於元素節點,nodeName 中保存的始終都是元素的標籤名,而 nodeValue 的值則始終爲 null。 

2. 節點關係 

文檔中所有的節點之間都存在這樣或那樣的關係。每個節點都有一個 childNodes 屬性,其中保存着一個 NodeList 對象。NodeList 是一種類數組 對象,用於保存一組有序的節點,可以通過位置來訪問這些節點。請注意,雖然可以通過方括號語法來 訪問 NodeList 的值,而且這個對象也有 length 屬性,但它並不是 Array 的實例。NodeList 對象的 獨特之處在於,它實際上是基於 DOM結構動態執行查詢的結果,因此 DOM結構的變化能夠自動反映 在 NodeList 對象中。我們常說,NodeList 是有生命、有呼吸的對象,而不是在我們第一次訪問它們 的某個瞬間拍攝下來的一張快照。 

下面的例子展示瞭如何訪問保存在 NodeList 中的節點——可以通過方括號,也可以使用 item() 方法。 

var firstChild = someNode.childNodes[0];
 var secondChild = someNode.childNodes.item(1);
 var count = someNode.childNodes.length; 
 

無論使用方括號還是使用 item()方法都沒有問題,但使用方括號語法看起來與訪問數組相似,因 此頗受一些開發人員的青睞。另外,要注意 length 屬性表示的是訪問 NodeList 的那一刻,其中包含 的節點數量。我們在本書前面介紹過,對 arguments 對象使用 Array.prototype.slice()方法可以 將其轉換爲數組。而採用同樣的方法,也可以將 NodeList 對象轉換爲數組。來看下面的例子: 

//在 IE8及之前版本中無效 
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0); 

除 IE8 及更早版本之外,這行代碼能在任何瀏覽器中運行。由於 IE8 及更早版本將 NodeList 實現爲一個 COM 對象,而我們不能像使用 JScript 對象那樣使用這種對象,因此上面的代碼會導致 錯誤。要想在 IE中將 NodeList 轉換爲數組,必須手動枚舉所有成員。下列代碼在所有瀏覽器中都 可以運行: 

function convertToArray(nodes){
     var array = null;
     try {
         array = Array.prototype.slice.call(nodes, 0);
 //針對非 IE瀏覽器
     } catch (ex) {
         array = new Array();
         for (var i=0, len=nodes.length; i < len; i++){
             array.push(nodes[i]);
         }
     } 
 
    return array;
 } 
 

這個 convertToArray()函數首先嚐試了創建數組的簡單方式。如果導致了錯誤(說明是在 IE8及更早版本中),則通過 try-catch 塊來捕獲錯誤,然後手動創建數組。這是另一種檢測怪癖的 形式。 

每個節點都有一個 parentNode 屬性,該屬性指向文檔樹中的父節點。包含在 childNodes 列表中 的所有節點都具有相同的父節點,因此它們的 parentNode 屬性都指向同一個節點。此外,包含在 childNodes 列表中的每個節點相互之間都是同胞節點。通過使用列表中每個節點的 previousSibling 和 nextSibling 屬性,可以訪問同一列表中的其他節點。列表中第一個節點的 previousSibling 屬性 值爲 null,而列表中後一個節點的 nextSibling 屬性的值同樣也爲 null,如下面的例子所示: 

if (someNode.nextSibling === null){
     alert("Last node in the parent’s childNodes list.");
 } else if (someNode.previousSibling === null){
     alert("First node in the parent’s childNodes list.");
 } 

當然,如果列表中只有一個節點,那麼該節點的 nextSibling 和 previousSibling 都爲 null。
 父節點與其第一個和後一個子節點之間也存在特殊關係。父節點的 firstChild 和 lastChild 屬性分別指向其 childNodes 列表中的第一個和後一個節點。其中,someNode.firstChild 的值 始終等於 someNode.childNodes[0],而 someNode.lastChild 的值始終等於 someNode. childNodes [someNode.childNodes.length-1]。

在只有一個子節點的情況下,firstChild 和 lastChild 指向同一個節點。如果沒有子節點,那麼 firstChild 和 lastChild 的值均爲 null。明 確這些關係能夠對我們查找和訪問文檔結構中的節點提供極大的便利。圖 10-2形象地展示了上述關係。

在反映這些關係的所有屬性當中,childNodes 屬性與其他屬性相比更方便一些,因爲只須使用簡 單的關係指針,就可以通過它訪問文檔樹中的任何節點。另外,hasChildNodes()也是一個非常有用 的方法,這個方法在節點包含一或多個子節點的情況下返回 true;應該說,這是比查詢 childNodes 列表的 length 屬性更簡單的方法。

所有節點都有的後一個屬性是 ownerDocument,該屬性指向表示整個文檔的文檔節點。這種關 系表示的是任何節點都屬於它所在的文檔,任何節點都不能同時存在於兩個或更多個文檔中。通過這個 屬性,我們可以不必在節點層次中通過層層回溯到達頂端,而是可以直接訪問文檔節點。 

雖然所有節點類型都繼承自 Node,但並不是每種節點都有子節點。本章後面將 會討論不同節點類型之間的差異。 

3. 操作節點 

因爲關係指針都是隻讀的,所以 DOM 提供了一些操作節點的方法。其中,常用的方法是 appendChild(),用於向 childNodes 列表的末尾添加一個節點。添加節點後,childNodes 的新增 節點、父節點及以前的後一個子節點的關係指針都會相應地得到更新。更新完成後,appendChild() 返回新增的節點。來看下面的例子: 

var returnedNode = someNode.appendChild(newNode);
 alert(returnedNode == newNode);
         //true
 alert(someNode.lastChild == newNode);
   //true 

如果傳入到 appendChild()中的節點已經是文檔的一部分了,那結果就是將該節點從原來的位置 轉移到新位置。即使可以將 DOM樹看成是由一系列指針連接起來的,但任何 DOM節點也不能同時出 現在文檔中的多個位置上。因此,如果在調用 appendChild()時傳入了父節點的第一個子節點,那麼 該節點就會成爲父節點的後一個子節點,如下面的例子所示。 

//someNode 有多個子節點
 var returnedNode = someNode.appendChild(someNode.firstChild);
 alert(returnedNode == someNode.firstChild);
      //false
 alert(returnedNode == someNode.lastChild); 
     //true 

如果需要把節點放在 childNodes 列表中某個特定的位置上,而不是放在末尾,那麼可以使用 insertBefore()方法。這個方法接受兩個參數:要插入的節點和作爲參照的節點。插入節點後,被插 入的節點會變成參照節點的前一個同胞節點(previousSibling),同時被方法返回。如果參照節點是 null,則 insertBefore()與 appendChild()執行相同的操作,如下面的例子所示。 
 

//插入後成爲最後一個子節點
 returnedNode = someNode.insertBefore(newNode, null); alert(newNode ==
 someNode.lastChild);  //true 
 
//插入後成爲第一個子節點
 var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
 alert(returnedNode == newNode);
   //true
 alert(newNode == someNode.firstChild);
  //true 
 
//插入到最後一個子節點前面
 returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
 alert(newNode == someNode.childNodes[someNode.childNodes.length-2]);
 //true 

前面介紹的 appendChild()和 insertBefore()方法都只插入節點,不會移除節點。而下面要介 紹的 replaceChild()方法接受的兩個參數是:要插入的節點和要替換的節點。要替換的節點將由這個 方法返回並從文檔樹中被移除,同時由要插入的節點佔據其位置。來看下面的例子。 

//替換第一個子節點
 var returnedNode = someNode.replaceChild(newNode, someNode.firstChild); 
 
//替換最後一個子節點
 returnedNode = someNode.replaceChild(newNode, someNode.lastChild); 

在使用 replaceChild()插入一個節點時,該節點的所有關係指針都會從被它替換的節點複製過 來。儘管從技術上講,被替換的節點仍然還在文檔中,但它在文檔中已經沒有了自己的位置。 如果只想移除而非替換節點,可以使用 removeChild()方法。這個方法接受一個參數,即要移除 的節點。被移除的節點將成爲方法的返回值,如下面的例子所示。 

//移除第一個子節點
 var formerFirstChild = someNode.removeChild(someNode.firstChild); 
 
//移除最後一個子節點
 var formerLastChild = someNode.removeChild(someNode.lastChild); 
 

與使用 replaceChild()方法一樣,通過 removeChild()移除的節點仍然爲文檔所有,只不過在 文檔中已經沒有了自己的位置。

前面介紹的四個方法操作的都是某個節點的子節點,也就是說,要使用這幾個方法必須先取得父節 點(使用 parentNode 屬性)。另外,並不是所有類型的節點都有子節點,如果在不支持子節點的節點 上調用了這些方法,將會導致錯誤發生。 

4. 其他方法 

有兩個方法是所有類型的節點都有的。第一個就是 cloneNode(),用於創建調用這個方法的節點 的一個完全相同的副本。cloneNode()方法接受一個布爾值參數,表示是否執行深複製。在參數爲 true 的情況下,執行深複製,也就是複製節點及其整個子節點樹;在參數爲 false 的情況下,執行淺複製, 即只複製節點本身。複製後返回的節點副本屬於文檔所有,但並沒有爲它指定父節點。因此,這個節點 副本就成爲了一個“孤兒”,除非通過 appendChild()、insertBefore()或 replaceChild()將它添加到文檔中。例如,假設有下面的 HTML代碼。 
 

<ul>
     <li>item 1</li>
     <li>item 2</li>
     <li>item 3</li>
 </ul> 

如果我們已經將<ul>元素的引用保存在了變量 myList 中,那麼通常下列代碼就可以看出使用 cloneNode()方法的兩種模式。 

var deepList = myList.cloneNode(true);
 alert(deepList.childNodes.length);
   //3(IE < 9)或 7(其他瀏覽器) 
 
var shallowList = myList.cloneNode(false);
 alert(shallowList.childNodes.length);  //0 

在這個例子中,deepList 中保存着一個對 myList 執行深複製得到的副本。因此,deepList 中 包含 3個列表項,每個列表項中都包含文本。而變量 shallowList 中保存着對 myList 執行淺複製得 到的副本,因此它不包含子節點。deepList.childNodes.length 中的差異主要是因爲 IE8及更早版 本與其他瀏覽器處理空白字符的方式不一樣。IE9之前的版本不會爲空白符創建節點。 

cloneNode()方法不會複製添加到 DOM節點中的 JavaScript屬性,例如事件處 理程序等。這個方法只複製特性、(在明確指定的情況下也複製)子節點,其他一切 都不會複製。IE在此存在一個 bug,即它會複製事件處理程序,所以我們建議在複製 之前最好先移除事件處理程序。 

我們要介紹的最後一個方法是 normalize(),這個方法唯一的作用就是處理文檔樹中的文本節點。 由於解析器的實現或 DOM操作等原因,可能會出現文本節點不包含文本,或者接連出現兩個文本節點 的情況。當在某個節點上調用這個方法時,就會在該節點的後代節點中查找上述兩種情況。如果找到了 空文本節點,則刪除它;如果找到相鄰的文本節點,則將它們合併爲一個文本節點。本章後面還將進一 步討論這個方法。 

 

Document類型 

JavaScript通過 Document 類型表示文檔。在瀏覽器中,document 對象是 HTMLDocument(繼承 自 Document 類型)的一個實例,表示整個 HTML頁面。而且,document 對象是 window 對象的一個 屬性,因此可以將其作爲全局對象來訪問。Document 節點具有下列特徵: 

  •      nodeType 的值爲 9;
  •  nodeName 的值爲"#document";
  •  nodeValue 的值爲 null;
  •  parentNode 的值爲 null;
  •  ownerDocument 的值爲 null;
  •  其子節點可能是一個 DocumentType(多一個)、Element(多一個)、ProcessingInstruction 或 Comment。 

Document 類型可以表示 HTML 頁面或者其他基於 XML 的文檔。不過,常見的應用還是作爲 HTMLDocument 實例的 document 對象。通過這個文檔對象,不僅可以取得與頁面有關的信息,而且還 能操作頁面的外觀及其底層結構。 

在 Firefox、Safari、Chrome和 Opera中,可以通過腳本訪問 Document 類型的構 造函數和原型。但在所有瀏覽器中都可以訪問 HTMLDocument 類型的構造函數和原型, 包括IE8及後續版本。 

1. 文檔的子節點 

雖然 DOM標準規定 Document 節點的子節點可以是 DocumentType、Element、ProcessingIn- struction 或 Comment,但還有兩個內置的訪問其子節點的快捷方式。第一個就是 documentElement 屬性,該屬性始終指向 HTML頁面中的<html>元素。另一個就是通過 childNodes 列表訪問文檔元素, 但通過 documentElement 屬性則能更快捷、更直接地訪問該元素。以下面這個簡單的頁面爲例。 

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

這個頁面在經過瀏覽器解析後,其文檔中只包含一個子節點,即<html>元素。可以通過 documentElement 或 childNodes 列表來訪問這個元素,如下所示。 

var html = document.documentElement;      //取得對<html>的引用
 alert(html === document.childNodes[0]);     //true
 alert(html === document.firstChild);      //true 

這個例子說明,documentElement、firstChild 和 childNodes[0]的值相同,都指向<html> 元素。

作爲 HTMLDocument 的實例,document 對象還有一個 body 屬性,直接指向<body>元素。

因爲開 發人員經常要使用這個元素,所以 document.body 在JavaScript代碼中出現的頻率非常高,其用法如下。
 

var body = document.body;    //取得對<body>的引用 

所有瀏覽器都支持 document.documentElement 和 document.body 屬性。 Document 另一個可能的子節點是 DocumentType。通常將<!DOCTYPE>標籤看成一個與文檔其他 部分不同的實體,可以通過 doctype 屬性(在瀏覽器中是 document.doctype)來訪問它的信息。 

var doctype = document.doctype;     //取得對<!DOCTYPE>的引用 

瀏覽器對 document.doctype 的支持差別很大,可以給出如下總結。 

  •     IE8 及之前版本:如果存在文檔類型聲明,會將其錯誤地解釋爲一個註釋並把它當作 Comment 節點;而 document.doctype 的值始終爲 null。
  •  IE9+及Firefox:如果存在文檔類型聲明,則將其作爲文檔的第一個子節點;document.doctype 是一個DocumentType 節點,也可以通過document.firstChild 或document.childNodes[0] 訪問同一個節點。
  •  Safari、Chrome和 Opera:如果存在文檔類型聲明,則將其解析,但不作爲文檔的子節點。docu- ment.doctype 是一個 DocumentType 節點,但該節點不會出現在 document.childNodes 中。

 由於瀏覽器對 document.doctype 的支持不一致,因此這個屬性的用處很有限。 從技術上說,出現在<html>元素外部的註釋應該算是文檔的子節點。然而,不同的瀏覽器在是否 解析這些註釋以及能否正確處理它們等方面,也存在很大差異。以下面簡單的 HTML頁面爲例。 

<!--第一條註釋 -->
 <html> 
    <body> 
    </body> 
</html> <!--第二條註釋 --> 

看起來這個頁面應該有 3 個子節點:註釋、<html>元素、註釋。從邏輯上講,我們會認爲 document.childNodes 中應該包含與這 3 個節點對應的 3 項。但是,現實中的瀏覽器在處理位於 <html>外部的註釋方面存在如下差異。 

  •  IE8及之前版本、Safari 3.1及更高版本、Opera和 Chrome只爲第一條註釋創建節點,不爲第二 條註釋創建節點。結果,第一條註釋就會成爲 document.childNodes 中的第一個子節點。
  • IE9 及更高版本會將第一條註釋創建爲 document.childNodes 中的一個註釋節點,也會將第 二條註釋創建爲 document.childNodes 中的註釋子節點。 
  • Firefox以及 Safari 3.1之前的版本會完全忽略這兩條註釋。 

同樣,瀏覽器間的這種不一致性也導致了位於<html>元素外部的註釋沒有什麼用處。 多數情況下,我們都用不着在 document 對象上調用 appendChild()、removeChild()和 replaceChild()方法,因爲文檔類型(如果存在的話)是隻讀的,而且它只能有一個元素子節點(該 節點通常早就已經存在了)。 

2. 文檔信息 

作爲 HTMLDocument 的一個實例,document 對象還有一些標準的 Document 對象所沒有的屬性。 這些屬性提供了 document 對象所表現的網頁的一些信息。其中第一個屬性就是 title,包含着 <title>元素中的文本——顯示在瀏覽器窗口的標題欄或標籤頁上。通過這個屬性可以取得當前頁面的 標題,也可以修改當前頁面的標題並反映在瀏覽器的標題欄中。修改 title 屬性的值不會改變<title> 元素。來看下面的例子。 
 

//取得文檔標題
 var originalTitle = document.title; 
 
//設置文檔標題
 document.title = "New page title"; 

接下來要介紹的 3個屬性都與對網頁的請求有關,它們是 URL、domain 和 referrer。URL 屬性 中包含頁面完整的 URL(即地址欄中顯示的 URL),domain 屬性中只包含頁面的域名,而 referrer 屬性中則保存着鏈接到當前頁面的那個頁面的 URL。在沒有來源頁面的情況下,referrer 屬性中可能 會包含空字符串。所有這些信息都存在於請求的 HTTP 頭部,只不過是通過這些屬性讓我們能夠在 JavaScrip中訪問它們而已,如下面的例子所示。 

//取得完整的
URL var url = document.URL; 
 
//取得域名
var domain = document.domain; 
 
//取得來源頁面的 URL var referrer = document.referrer; 

URL 與 domain 屬性是相互關聯的。例如,如果 document.URL 等於 http://www.wrox.com/WileyCDA/,
 那麼 document.domain 就等於 www.wrox.com。 在這 3個屬性中,只有 domain 是可以設置的。但由於安全方面的限制,也並非可以給 domain 設 置任何值。如果 URL中包含一個子域名,例如 p2p.wrox.com,那麼就只能將 domain 設置爲"wrox.com" (URL 中包含"www",如 www.wrox.com 時,也是如此)。不能將這個屬性設置爲 URL 中不包含的域, 如下面的例子所示。 

//假設頁面來自 p2p.wrox.com 域 
 
document.domain = "wrox.com";          // 成功 
 
document.domain = "nczonline.net";      // 出錯! 

當頁面中包含來自其他子域的框架或內嵌框架時,能夠設置 document.domain 就非常方便了。由 於跨域安全限制,來自不同子域的頁面無法通過 JavaScript 通信。而通過將每個頁面的 document.domain 設置爲相同的值,這些頁面就可以互相訪問對方包含的 JavaScript 對象了。例如, 假設有一個頁面加載自 www.wrox.com,其中包含一個內嵌框架,框架內的頁面加載自 p2p.wrox.com。 由於 document.domain 字符串不一樣,內外兩個頁面之間無法相互訪問對方的 JavaScript對象。但如 果將這兩個頁面的 document.domain 值都設置爲"wrox.com",它們之間就可以通信了。 瀏覽器對 domain 屬性還有一個限制,即如果域名一開始是“鬆散的”(loose),那麼不能將它再設 置爲“緊繃的”(tight)。換句話說,在將 document.domain 設置爲"wrox.com"之後,就不能再將其 設置回"p2p.wrox.com",否則將會導致錯誤,如下面的例子所示。 

//假設頁面來自於 p2p.wrox.com 域 
 
document.domain = "wrox.com";         //鬆散的(成功) 
 
document.domain = "p2p.wrox.com";     //緊繃的(出錯!) 

所有瀏覽器中都存在這個限制,但 IE8是實現這一限制的早的 IE版本

3. 查找元素 

說到常見的 DOM應用,恐怕就要數取得特定的某個或某組元素的引用,然後再執行一些操作了。 取得元素的操作可以使用 document 對象的幾個方法來完成。其中,Document 類型爲此提供了兩個方 法:getElementById()和 getElementsByTagName()。

第一個方法,getElementById(),接收一個參數:要取得的元素的 ID。如果找到相應的元素則 返回該元素,如果不存在帶有相應 ID的元素,則返回 null。注意,這裏的 ID必須與頁面中元素的 id 特性(attribute)嚴格匹配,包括大小寫。以下面的元素爲例。 

<div id="myDiv">Some text</div> 
 

可以使用下面的代碼取得這個元素: 

var div = document.getElementById("myDiv");        //取得<div>元素的引用 

但是,下面的代碼在除 IE7及更早版本之外的所有瀏覽器中都將返回 null

var div = document.getElementById("mydiv");        //無效的 ID(在 IE7及更早版本中可以) 

IE8及較低版本不區分 ID的大小寫,因此"myDiv"和"mydiv"會被當作相同的元素 ID。 如果頁面中多個元素的 ID值相同,getElementById()只返回文檔中第一次出現的元素。IE7及較 低版本還爲此方法添加了一個有意思的“怪癖”:name 特性與給定 ID 匹配的表單元素(<input>、 <textarea>、<button>及<select>)也會被該方法返回。如果有哪個表單元素的 name 特性等於指 定的 ID,而且該元素在文檔中位於帶有給定 ID的元素前面,那麼 IE就會返回那個表單元素。來看下面 的例子。 

<input type="text" name="myElement" value="Text field">
 <div id="myElement">A div</div> 
 

基於這段 HTML代碼,在 IE7中調用 document.getElementById("myElement "),結果會返 回<input>元素;而在其他所有瀏覽器中,都會返回對<div>元素的引用。爲了避免 IE中存在的這個問 題,好的辦法是不讓表單字段的 name 特性與其他元素的 ID相同。 

另一個常用於取得元素引用的方法是 getElementsByTagName()。這個方法接受一個參數,即要 取得元素的標籤名,而返回的是包含零或多個元素的 NodeList。在 HTML文檔中,這個方法會返回一 個 HTMLCollection 對象,作爲一個“動態”集合,該對象與 NodeList 非常類似。例如,下列代碼 會取得頁面中所有的<img>元素,並返回一個 HTMLCollection。 

var images = document.getElementsByTagName("img"); 

這行代碼會將一個 HTMLCollection 對象保存在 images 變量中。與 NodeList 對象類似,可以 使用方括號語法或 item()方法來訪問 HTMLCollection 對象中的項。而這個對象中元素的數量則可以 通過其 length 屬性取得,如下面的例子所示。 

alert(images.length);          //輸出圖像的數量
 alert(images[0].src);          //輸出第一個圖像元素的 src 特性
 alert(images.item(0).src);     //輸出第一個圖像元素的 src 特性 

HTMLCollection 對象還有一個方法,叫做 namedItem(),使用這個方法可以通過元素的 name 特性取得集合中的項。例如,假設上面提到的頁面中包含如下<img>元素: 

<img src="myimage.gif" name="myImage"> 

那麼就可以通過如下方式從 images 變量中取得這個<img>元素: 

var myImage = images.namedItem("myImage"); 

在提供按索引訪問項的基礎上,HTMLCollection 還支持按名稱訪問項,這就爲我們取得實際想要 的元素提供了便利。而且,對命名的項也可以使用方括號語法來訪問,如下所示: 

var myImage = images["myImage"]; 

對 HTMLCollection 而言,我們可以向方括號中傳入數值或字符串形式的索引值。在後臺,對數 值索引就會調用 item(),而對字符串索引就會調用 namedItem()。 要想取得文檔中的所有元素,可以向 getElementsByTagName()中傳入"*"。在 JavaScript及 CSS 中,星號(*)通常表示“全部”。下面看一個例子。 


 var allElements = document.getElementsByTagName("*"); 

僅此一行代碼返回的 HTMLCollection 中,就包含了整個頁面中的所有元素——按照它們出現的 先後順序。換句話說,第一項是<html>元素,第二項是<head>元素,以此類推。由於 IE將註釋(Comment) 實現爲元素(Element),因此在 IE中調用 getElementsByTagName("*")將會返回所有註釋節點。 

雖然標準規定標籤名需要區分大小寫,但爲了最大限度地與既有 HTML 頁面兼 容,傳給 getElementsByTagName()的標籤名是不需要區分大小寫的。但對於 XML 頁面而言(包括 XHTML),getElementsByTagName()方法就會區分大小寫。 

第三個方法,也是隻有 HTMLDocument 類型纔有的方法,是 getElementsByName()。顧名思義, 這個方法會返回帶有給定 name 特性的所有元素。常使用 getElementsByName()方法的情況是取得 單選按鈕;爲了確保發送給瀏覽器的值正確無誤,所有單選按鈕必須具有相同的 name 特性,如下面的 例子所示。 


 <fieldset>
     <legend>Which color do you prefer?</legend>
     <ul>
         <li><input type="radio" value="red" name="color" id="colorRed">
             <label for="colorRed">Red</label></li>
         <li><input type="radio" value="green" name="color" id="colorGreen">
             <label for="colorGreen">Green</label></li>
         <li><input type="radio" value="blue" name="color" id="colorBlue">
             <label for="colorBlue">Blue</label></li>
     </ul>
 </fieldset> 

如這個例子所示,其中所有單選按鈕的 name 特性值都是"color",但它們的 ID可以不同。ID的 作用在於將<label>元素應用到每個單選按鈕,而 name 特性則用以確保三個值中只有一個被髮送給瀏 覽器。這樣,我們就可以使用如下代碼取得所有單選按鈕: 

var radios = document.getElementsByName("color"); 

與 getElementsByTagName()類似,getElementsByName()方法也會返回一個 HTMLCollectioin。 但是,對於這裏的單選按鈕來說,namedItem()方法則只會取得第一項(因爲每一項的 name 特性都相同)。

4. 特殊集合 

除了屬性和方法,document 對象還有一些特殊的集合。這些集合都是 HTMLCollection 對象, 爲訪問文檔常用的部分提供了快捷方式,包括:

  •      document.anchors,包含文檔中所有帶 name 特性的<a>元素;
  •  document.applets,包含文檔中所有的<applet>元素,因爲不再推薦使用<applet>元素, 所以這個集合已經不建議使用了;
  •  document.forms,包含文檔中所有的<form>元素,與 document.getElementsByTagName("form") 得到的結果相同;
  •  document.images,包含文檔中所有的<img>元素,與 document.getElementsByTagName ("img")得到的結果相同;
  •  document.links,包含文檔中所有帶 href 特性的<a>元素。 

這個特殊集合始終都可以通過 HTMLDocument 對象訪問到,而且,與 HTMLCollection 對象類似, 集合中的項也會隨着當前文檔內容的更新而更新。 


5. DOM一致性檢測

由於 DOM分爲多個級別,也包含多個部分,因此檢測瀏覽器實現了 DOM的哪些部分就十分必要 了。document.implementation 屬性就是爲此提供相應信息和功能的對象,與瀏覽器對 DOM的實現 直接對應。DOM1級只爲 document.implementation 規定了一個方法,即 hasFeature()。這個方 法接受兩個參數:要檢測的 DOM功能的名稱及版本號。如果瀏覽器支持給定名稱和版本的功能,則該 方法返回 true,如下面的例子所示: 

var hasXmlDom = document.implementation.hasFeature("XML", "1.0"); 
 

下表列出了可以檢測的不同的值及版本號。 

儘管使用 hasFeature()確實方便,但也有缺點。因爲實現者可以自行決定是否與 DOM規範的不 同部分保持一致。事實上,要想讓 hasFearture()方法針對所有值都返回 true 很容易,但返回 true 有時候也不意味着實現與規範一致。例如,Safari 2.x及更早版本會在沒有完全實現某些 DOM功能的情 況下也返回 true。爲此,我們建議多數情況下,在使用 DOM 的某些特殊的功能之前,好除了檢測 hasFeature()之外,還同時使用能力檢測。

6. 文檔寫入 

有一個 document 對象的功能已經存在很多年了,那就是將輸出流寫入到網頁中的能力。這個能力 體現在下列 4個方法中:write()、writeln()、open()和 close()。其中,write()和 writeln() 方法都接受一個字符串參數,即要寫入到輸出流中的文本。write()會原樣寫入,而 writeln()則會 在字符串的末尾添加一個換行符(\n)。在頁面被加載的過程中,可以使用這兩個方法向頁面中動態地 加入內容,如下面的例子所示。 

<html>
 <head>
     <title>document.write() Example</title> </head>
 <body>
     <p>The current date and time is:
     <script type="text/javascript">
         document.write("<strong>" + (new Date()).toString() + "</strong>");
     </script>
     </p>
 </body>
 </html> 

這個例子展示了在頁面加載過程中輸出當前日期和時間的代碼。其中,日期被包含在一個<strong> 元素中,就像在 HTML頁面中包含普通的文本一樣。這樣做會創建一個 DOM元素,而且可以在將來訪 問該元素。通過 write()和 writeln()輸出的任何 HTML代碼都將如此處理。 此外,還可以使用 write()和 writeln()方法動態地包含外部資源,例如 JavaScript文件等。在包 含 JavaScript文件時,必須注意不能像下面的例子那樣直接包含字符串"</script>",因爲這會導致該 字符串被解釋爲腳本塊的結束,它後面的代碼將無法執行。 

<html>
 <head>
     <title>document.write() Example 2</title>
 </head>
 <body>
     <script type="text/javascript">
         document.write("<script type=\"text/javascript\" src=\"file.js\">" +
                "</script>");
     </script>
 </body>
 </html> 
 

即使這個文件看起來沒錯,但字符串"</script>"將被解釋爲與外部的<script>標籤匹配,結果 文本");將會出現在頁面中。爲避免這個問題,只需加入轉義字符\即可;第 2章也曾經提及這個問題, 解決方案如下。 

<html>
 <head>
     <title>document.write() Example 3</title>
 </head>
 <body>
     <script type="text/javascript">
         document.write("<script type=\"text/javascript\" src=\"file.js\">" +
                "<\/script>");
     </script>
 </body>
 </html> 
 

字符串"<\/script>"不會被當作外部<script>標籤的關閉標籤,因而頁面中也就不會出現多餘 的內容了。 

前面的例子使用 document.write()在頁面被呈現的過程中直接向其中輸出了內容。如果在文檔 加載結束後再調用 document.write(),那麼輸出的內容將會重寫整個頁面,如下面的例子所示: 
 

<html>
 <head>
     <title>document.write() Example 4</title>
 </head>
 <body>
     <p>This is some content that you won't get to see because it will be overwritten.

    </p>
     <script type="text/javascript">
         window.onload = function(){
             document.write("Hello world!");
         };
     </script>
 </body>
 </html> 
 

在這個例子中,我們使用了 window.onload 事件處理程序(事件將在第 13章討論),等到頁面完 全加載之後延遲執行函數。函數執行之後,字符串"Hello world!"會重寫整個頁面內容。 方法 open()和 close()分別用於打開和關閉網頁的輸出流。如果是在頁面加載期間使用 write() 或 writeln()方法,則不需要用到這兩個方法。 

嚴格型 XHTML文檔不支持文檔寫入。對於那些按照 application/xml+xhtml 內容類型提供的頁面,這兩個方法也同樣無效。 

 

Element類型 

除了Document類型之外,Element類型就要算是 Web 編程中 常用的類型了。Element類型用於表現 XML或 HTML元素,提供了對元素標籤名、子節點及特性的訪問。Element節點具有以下特徵:

  1. nodeType的值爲 1;
  2. nodeName的值爲元素的標籤名;
  3. nodeValue的值爲null;
  4. parentNode可能是Document或Element;
  5. 其子節點可能是Element、Text、Comment、ProcessingInstruction、CDATASection或

要訪問元素的標籤名,可以使用nodeName屬性,也可以使用tagName屬性;這兩個屬性會返回相同的值(使用後者主要是爲了清晰起見)。以下面的元素爲例:

 

<div id="myDiv"></div>

可以像下面這樣取得這個元素及其標籤名:

 var div = document.getElementById("myDiv"); 
alert(div.tagName);  

   //"DIV"
 alert(div.tagName == div.nodeName); //true

這裏的元素標籤名是div,它擁有一個值爲"myDiv"的 ID。可是,div.tagName實際上輸出的是 "DIV"而非"div"。在 HTML 中,標籤名始終都以全部大寫表示;而在 XML(有時候也包括 XHTML)中,標籤名則始終會與源代碼中的保持一致。假如你不確定自己的腳本將會在 HTML 還是 XML 文檔中執行, 好是在比較之前將標籤名轉換爲相同的大小寫形式,如下面的例子所示:

 if (element.tagName == "div"){ 
//不能這樣比較,很容易出錯!
     //在此執行某些操作

} 

if (element.tagName.toLowerCase() == "div"){
 //這樣最好(適用於任何文檔)
     //在此執行某些操作

}

 

這個例子展示了圍繞 tagName 屬性的兩次比較操作。第一次比較非常容易出錯,因爲其代碼在 HTML 文檔中不管用。第二次比較將標籤名轉換成了全部小寫,是我們推薦的做法,因爲這種做法適用於 HTML 文檔,也適用於 XML 文檔。

可以在任何瀏覽器中通過腳本訪問Element類型的構造函數及原型,包括 IE8及之前版本。在 Safari 2 之前版本和 Opera 8 之前的版本中,不能訪問Element類型的構造函數。

1. HTML 元素

所有 HTML 元素都由HTMLElement類型表示,不是直接通過這個類型,也是通過它的子類型來表示。HTMLElement類型直接繼承自Element並添加了一些屬性。添加的這些屬性分別對應於每個HTML 元素中都存在的下列標準特性。

  1. id,元素在文檔中的唯一標識符。
  2. title,有關元素的附加說明信息,一般通過工具提示條顯示出來。
  3. lang,元素內容的語言代碼,很少使用。
  4. dir,語言的方向,值爲"ltr"(left-to-right,從左至右)或"rtl"(right-to-left,從右至左),也很少使用。
  5. className,與元素的class特性對應,即爲元素指定的CSS類。沒有將這個屬性命名爲class,是因爲class是 ECMAScript 的保留字(有關保留字的信息,請參見第 1 章)。上述這些屬性都可以用來取得或修改相應的特性值。以下面的 HTML 元素爲例:

 

<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

 

元素中指定的所有信息,都可以通過下列 JavaScript 代碼取得:

 var div = document.getElementById("myDiv");
 alert(div.id);          //"myDiv""
 alert(div.className); 
      //"bd"
 alert(div.title);
         //"Body text"
 alert(div.lang);
         //"en"
 alert(div.dir);         //"ltr"

 

當然,像下面這樣通過爲每個屬性賦予新的值,也可以修改對應的每個特性:

 

div.id = "someOtherId";
 div.className = "ft";
 div.title = "Some other text";
 div.lang = "fr";
 div.dir ="rtl"; 

 

並不是對所有屬性的修改都會在頁面中直觀地表現出來。對id或lang的修改對用戶而言是透明

不可見的(假設沒有基於它們的值設置的 CSS 樣式),而對title的修改則只會在鼠標移動到這個元素 3 之上時纔會顯示出來。對dir的修改會在屬性被重寫的那一刻,立即影響頁面中文本的左、右對齊方式。修改className時,如果新類關聯了與此前不同的 CSS 樣式,那麼就會立即應用新的樣式。

前面提到過,所有 HTML 元素都是由HTMLElement或者其更具體的子類型來表示的。下表列出了所有 HTML 元素以及與之關聯的類型(以斜體印刷的元素表示已經不推薦使用了)。注意,表中的這些類型在 Opera、Safari、Chrome 和 Firefox 中都可以通過 JavaScript 訪問,但在 IE8 之前的版本中不能通過 JavaScript 訪問。

元  素

類  型

元  素

類  型

A

HTMLAnchorElement

EM

HTMLElement

ABBR

HTMLElement

FIELDSET

HTMLFieldSetElement

ACRONYM

HTMLElement

FONT

HTMLFontElement

ADDRESS

HTMLElement

FORM

HTMLFormElement

APPLET

HTMLAppletElement

FRAME

HTMLFrameElement

AREA

HTMLAreaElement

FRAMESET

HTMLFrameSetElement

B

HTMLElement

H1

HTMLHeadingElement

BASE

HTMLBaseElement

H2

HTMLHeadingElement

BASEFONT

HTMLBaseFontElement

H3

HTMLHeadingElement

BDO

HTMLElement

H4

HTMLHeadingElement

BIG

HTMLElement

H5

HTMLHeadingElement

BLOCKQUOTE

HTMLQuoteElement

H6

HTMLHeadingElement

BODY

HTMLBodyElement

HEAD

HTMLHeadElement

BR

HTMLBRElement

HR

HTMLHRElement

BUTTON

HTMLButtonElement

HTML

HTMLHtmlElement

CAPTION

HTMLTableCaptionElement

I

HTMLElement

CENTER

HTMLElement

IFRAME

HTMLIFrameElement

CITE

HTMLElement

IMG

HTMLImageElement

CODE

HTMLElement

INPUT

HTMLInputElement

COL

HTMLTableColElement

INS

HTMLModElement

COLGROUP

HTMLTableColElement

ISINDEX

HTMLIsIndexElement

DD

HTMLElement

KBD

HTMLElement

DEL

HTMLModElement

LABEL

HTMLLabelElement

DFN

HTMLElement

LEGEND

HTMLLegendElement

DIR

HTMLDirectoryElement

LI

HTMLLIElement

DIV

HTMLDivElement

LINK

HTMLLinkElement

DL

HTMLDListElement

MAP

HTMLMapElement

DT

HTMLElement

MENU

HTMLMenuElement

 

 

(續)

元  素

類  型

元  素

類  型

META

HTMLMetaElement

STRONG

HTMLElement

NOFRAMES

HTMLElement

STYLE

HTMLStyleElement

NOSCRIPT

HTMLElement

SUB

HTMLElement

OBJECT

HTMLObjectElement

SUP

HTMLElement

OL

HTMLOListElement

TABLE

HTMLTableElement

OPTGROUP

HTMLOptGroupElement

TBODY

HTMLTableSectionElement

OPTION

HTMLOptionElement

TD

HTMLTableCellElement

P

HTMLParagraphElement

TEXTAREA

HTMLTextAreaElement

PARAM

HTMLParamElement

TFOOT

HTMLTableSectionElement

PRE

HTMLPreElement

TH

HTMLTableCellElement

Q

HTMLQuoteElement

THEAD

HTMLTableSectionElement

S

HTMLElement

TITLE

HTMLTitleElement

SAMP

HTMLElement

TR

HTMLTableRowElement

SCRIPT

HTMLScriptElement

TT

HTMLElement

SELECT

HTMLSelectElement

U

HTMLElement

SMALL

HTMLElement

UL

HTMLUListElement

SPAN

HTMLElement

VAR

HTMLElement

STRIKE

HTMLElement

 

 

 

表中的每一種類型都有與之相關的特性和方法。本書將會討論其中很多類型。

2. 取得特性

每個元素都有一或多個特性,這些特性的用途是給出相應元素或其內容的附加信息。操作特性的

DOM 方法主要有三個,分別是getAttribute()、setAttribute()和removeAttribute()。這三個方法可以針對任何特性使用,包括那些以HTMLElement類型屬性的形式定義的特性。來看下面的例子:

 var div = document.getElementById("myDiv");
 alert(div.getAttribute("id"));
         //"myDiv"
 alert(div.getAttribute("class"));
      //"bd" 
alert(div.getAttribute("title"));      //"Body text" 
alert(div.getAttribute("lang"));       //"en"
 alert(div.getAttribute("dir"));        //"ltr"

 

注意,傳遞給getAttribute()的特性名與實際的特性名相同。因此要想得到class特性值,應該傳入"class"而不是"className",後者只有在通過對象屬性訪問特性時才用。如果給定名稱的特性不存在,getAttribute()返回null。

通過getAttribute()方法也可以取得自定義特性(即標準 HTML 語言中沒有的特性)的值,以

下面的元素爲例:

 

<div id="myDiv" my_special_attribute="hello!"></div>

 

這個元素包含一個名爲my_special_attribute 的自定義特性,它的值是"hello!"。可以像取

得其他特性一樣取得這個值,如下所示:

 var value = div.getAttribute("my_special_attribute");

 

不過,特性的名稱是不區分大小寫的,即"ID"和"id"代表的都是同一個特性。另外也要注意,根據 HTML5 規範,自定義特性應該加上data-前綴以便驗證。

任何元素的所有特性,也都可以通過 DOM 元素本身的屬性來訪問。當然,HTMLElement也會有 5 個屬性與相應的特性一一對應。不過,只有公認的(非自定義的)特性纔會以屬性的形式添加到 DOM 對象中。以下面的元素爲例:

 

<div id="myDiv" align="left" my_special_attribute="hello!"></div>

 

 


因爲id和align在 HTML 中是<div>的公認特性,因此該元素的 DOM 對象中也將存在對應的屬性。不過,自定義特性my_special_attribute在 Safari、Opera、Chrome 及 Firefox 中是不存在的;

但 IE 卻會爲自定義特性也創建屬性,如下面的例子所示:

 alert(div.id); 
                      //"myDiv"
 alert(div.my_special_attribute);      //undefined(IE 除外)
 alert(div.align);                   //"left" 

 

有兩類特殊的特性,它們雖然有對應的屬性名,但屬性的值與通過getAttribute()返回的值並不相同。第一類特性就是style,用於通過 CSS 爲元素指定樣式。在通過getAttribute()訪問時,返回的style特性值中包含的是 CSS 文本,而通過屬性來訪問它則會返回一個對象。由於style屬性是用於以編程方式訪問元素樣式的(本章後面討論),因此並沒有直接映射到style特性。

第二類與衆不同的特性是onclick這樣的事件處理程序。當在元素上使用時,onclick特性中包含的是 JavaScript 代碼,如果通過 getAttribute()訪問,則會返回相應代碼的字符串。而在訪問 onclick屬性時,則會返回一個 JavaScript 函數(如果未在元素中指定相應特性,則返回null)。這是因爲onclick及其他事件處理程序屬性本身就應該被賦予函數值。

由於存在這些差別,在通過 JavaScript 以編程方式操作 DOM 時,開發人員經常不使用getAttri- bute(),而是隻使用對象的屬性。只有在取得自定義特性值的情況下,纔會使用getAttribute()方法。

8

在 IE7及以前版本中,通過getAttribute()方法訪問style特性或onclick這樣的事件處理特性時,返回的值與屬性的值相同。換句話說,getAttribute("style")返回一個對象,而 getAttribute("onclick")返回一個函數。雖然 IE8 已經修復了這個 9 bug,但不同IE版本間的不一致性,也是導致開發人員不使用getAttribute()訪問HTML 特性的一個原因。

3.設置特性                                                                                                                           10

 

與getAttribute()對應的方法是setAttribute(),這個方法接受兩個參數:要設置的特性名和值。如果特性已經存在,setAttribute()會以指定的值替換現有的值;如果特性不存在,setAttribute()

則創建該屬性並設置相應的值。來看下面的例子:                                                                                                     11

 div.setAttribute("id", "someOtherId");
 div.setAttribute("class", "ft");

div.setAttribute("title", "Some other text");
 div.setAttribute("lang","fr"); 
div.setAttribute("dir", "rtl");

 

 

通過setAttribute()方法既可以操作 HTML 特性也可以操作自定義特性。通過這個方法設置的特性名會被統一轉換爲小寫形式,即"ID"終會變成"id"。

因爲所有特性都是屬性,所以直接給屬性賦值可以設置特性的值,如下所示。

 div.id = "someOtherId";

div.align = "left";

 

不過,像下面這樣爲 DOM 元素添加一個自定義的屬性,該屬性不會自動成爲元素的特性。

 

 div.mycolor = "red";

alert(div.getAttribute("mycolor")); //null(IE除外)

這個例子添加了一個名爲 mycolor 的屬性並將它的值設置爲"red"。在大多數瀏覽器中,這個屬性都不會自動變成元素的特性,因此想通過getAttribute()取得同名特性的值,結果會返回null。

可是,自定義屬性在 IE 中會被當作元素的特性,反之亦然。

在 IE7 及以前版本中,setAttribute()存在一些異常行爲。通過這個方法設置 class和style特性,沒有任何效果,而使用這個方法設置事件處理程序特性時也一樣。儘管到了 IE8 才解決這些問題,但我們還是推薦通過屬性來設置特性。

要介紹的 後一個方法是removeAttribute(),這個方法用於徹底刪除元素的特性。調用這個方法不僅會清除特性的值,而且也會從元素中完全刪除特性,如下所示:

 

div.removeAttribute("class");

 

這個方法並不常用,但在序列化 DOM 元素時,可以通過它來確切地指定要包含哪些特性。

 

4. attributes屬性

Element類型是使用attributes屬性的唯一一個 DOM 節點類型。attributes屬性中包含一個 NamedNodeMap,與NodeList類似,也是一個“動態”的集合。元素的每一個特性都由一個Attr節點表示,每個節點都保存在NamedNodeMap對象中。NamedNodeMap對象擁有下列方法。

  1. getNamedItem(name):返回nodeName屬性等於name的節點;
  2. removeNamedItem(name):從列表中移除nodeName屬性等於name的節點;
  3. setNamedItem(node):向列表中添加節點,以節點的nodeName屬性爲索引;
  4. item(pos):返回位於數字pos位置處的節點。

attributes屬性中包含一系列節點,每個節點的nodeName就是特性的名稱,而節點的nodeValue 就是特性的值。要取得元素的id特性,可以使用以下代碼。

 

var id = element.attributes.getNamedItem("id").nodeValue;

 

以下是使用方括號語法通過特性名稱訪問節點的簡寫方式。

 

var id = element.attributes["id"].nodeValue;

 

也可以使用這種語法來設置特性的值,即先取得特性節點,然後再將其nodeValue設置爲新值,如下所示。

element.attributes["id"].nodeValue = "someOtherId";

 

調用removeNamedItem()方法與在元素上調用removeAttribute()方法的效果相同——直接刪除具有給定名稱的特性。下面的例子展示了兩個方法間唯一的區別,即removeNamedItem()返回表示被刪除特性的Attr節點。

 var oldAttr = element.attributes.removeNamedItem("id"); 

最後,setNamedItem()是一個很不常用的方法,通過這個方法可以爲元素添加一個新特性,爲此需要爲它傳入一個特性節點,如下所示。

 
element.attributes.setNamedItem(newAttr);

 

一般來說,由於前面介紹的 attributes 的方法不夠方便,因此開發人員更多的會使用

getAttribute()、removeAttribute()和setAttribute()方法。

不過,如果想要遍歷元素的特性,attributes屬性倒是可以派上用場。在需要將 DOM 結構序列化爲 XML 或 HTML 字符串時,多數都會涉及遍歷元素特性。以下代碼展示瞭如何迭代元素的每一個特性,然後將它們構造成name="value" name="value"這樣的字符串格式。

 


 function outputAttributes(element){    
     var pairs = new Array(),   
     attrName,
     attrValue,
     i,
     len;

     for (i=0, len=element.attributes.length; i < len; i++){
         attrName = element.attributes[i].nodeName;
         attrValue = element.attributes[i].nodeValue;
         pairs.push(attrName + "=\"" + attrValue + "\"");


    }
     return pairs.join(" ");

}

 

這個函數使用了一個數組來保存名值對, 後再以空格爲分隔符將它們拼接起來(這是序列化長字符串時的一種常用技巧)。通過attributes.length屬性,for循環會遍歷每個特性,將特性的名稱和值輸出爲字符串。關於以上代碼的運行結果,以下是兩點必要的說明。     

針對attributes對象中的特性,不同瀏覽器返回的順序不同。這些特性在 XML 或 HTML 代碼中出現的先後順序,不一定與它們出現在attributes對象中的順序一致。
IE7 及更早的版本會返回 HTML 元素中所有可能的特性,包括沒有指定的特性。換句話說,返回 100 多個特性的情況會很常見。
 

針對 IE7 及更早版本中存在的問題,可以對上面的函數加以改進,讓它只返回指定的特性。每個特性節點都有一個名爲specified的屬性,這個屬性的值如果爲true,則意味着要麼是在 HTML 中指定了相應特性,要麼是通過setAttribute()方法設置了該特性。在 IE 中,所有未設置過的特性的該屬性值都爲false,而在其他瀏覽器中根本不會爲這類特性生成對應的特性節點(因此,在這些瀏覽器中,任何特性節點的specified值始終爲true)。改進後的代碼如下所示。

function outputAttributes(element){
     var pairs = new Array(),
         attrName,
         attrValue,
         i,
         len;

     for (i=0, len=element.attributes.length; i < len; i++){
         attrName = element.attributes[i].nodeName;
         attrValue = element.attributes[i].nodeValue;
         if (element.attributes[i].specified) {
             pairs.push(attrName + "=\"" + attrValue + "\"");


        }
     }
     return pairs.join(" ");

}

 

這個經過改進的函數可以確保即使在 IE7 及更早的版本中,也會只返回指定的特性。

5. 創建元素

使用document.createElement()方法可以創建新元素。這個方法只接受一個參數,即要創建元素的標籤名。這個標籤名在 HTML 文檔中不區分大小寫,而在 XML(包括 XHTML)文檔中,則是區分大小寫的。例如,使用下面的代碼可以創建一個<div>元素。

 var div = document.createElement("div");

 

在使用createElement()方法創建新元素的同時,也爲新元素設置了ownerDocuemnt屬性。此

時,還可以操作元素的特性,爲它添加更多子節點,以及執行其他操作。來看下面的例子。

 div.id = "myNewDiv";
 div.className = "box";

 

在新元素上設置這些特性只是給它們賦予了相應的信息。由於新元素尚未被添加到文檔樹中,因此

設置這些特性不會影響瀏覽器的顯示。要把新元素添加到文檔樹,可以使用appendChild()、inser- tBefore()或replaceChild()方法。下面的代碼會把新創建的元素添加到文檔的<body>元素中。

 document.body.appendChild(div);

 

 

一旦將元素添加到文檔樹中,瀏覽器就會立即呈現該元素。此後,對這個元素所作的任何修改都會

實時反映在瀏覽器中。在 IE 中可以以另一種方式使用createElement(),即爲這個方法傳入完整的元素標籤,也可以包

含屬性,如下面的例子所示。

 var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >");

 

這種方式有助於避開在 IE7 及更早版本中動態創建元素的某些問題。下面是已知的一些這類問題。

  1. 不能設置動態創建的<iframe>元素的name特性。
  2. 不能通過表單的reset()方法重設動態創建的<input>元素(第 13 章將討論reset()方法)。
  3. 動態創建的type特性值爲"reset"的<buttou>元素重設不了表單。
  4. 動態創建的一批name相同的單選按鈕彼此毫無關係。name值相同的一組單選按鈕本來應該用於表示同一選項的不同值,但動態創建的一批這種單選按鈕之間卻沒有這種關係。

上述所有問題都可以通過在createElement()中指定完整的HTML標籤來解決,如下面的例子所示。

 if (client.browser.ie && client.browser.ie <=7){ 

//創建一個帶name特性的iframe元素

    var iframe = document.createElement("<iframe name=\"myframe\"></iframe>"); 

    //創建input元素

    var input = document.createElement("<input type=\"checkbox\">"); 

    //創建button元素

    var button = document.createElement("<button type=\"reset\"></button>");

 

    //創建單選按鈕

    var radio1 = document.createElement("<input type=\"radio\" name=\"choice\" "+

"value=\"1\">");

    var radio2 = document.createElement("<input type=\"radio\" name=\"choice\" "+ 

"value=\"2\">");

}

 

與使用createElement()的慣常方式一樣,這樣的用法也會返回一個 DOM 元素的引用。可以將這個引用添加到文檔中,也可以對其加以增強。但是,由於這樣的用法要求使用瀏覽器檢測,因此我們建議只在需要避開 IE 及更早版本中上述某個問題的情況下使用。其他瀏覽器都不支持這種用法。

6. 元素的子節點

元素可以有任意數目的子節點和後代節點,因爲元素可以是其他元素的子節點。元素的 childNodes屬性中包含了它的所有子節點,這些子節點有可能是元素、文本節點、註釋或處理指令。

不同瀏覽器在看待這些節點方面存在顯著的不同,以下面的代碼爲例。

<ul id="myList">

    <li>Item 1</li>

    <li>Item 2</li>

    <li>Item 3</li>
</ul>

如果是 IE 來解析這些代碼,那麼<ul>元素會有 3 個子節點,分別是 3 個<li>元素。但如果是在其

 

他瀏覽器中,<ul>元素都會有 7 個元素,包括 3 個<li>元素和 4 個文本節點(表示<li>元素之間的空白符)。如果像下面這樣將元素間的空白符刪除,那麼所有瀏覽器都會返回相同數目的子節點。


<ul id="myList"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>

對於這段代碼,<ul>元素在任何瀏覽器中都會包含 3個子節點。如果需要通過 childNodes 屬性 遍歷子節點,那麼一定不要忘記瀏覽器間的這一差別。這意味着在執行某項操作以前,通常都要先檢查 一下 nodeTpye 屬性,如下面的例子所示。 

 

for (var i=0, len=element.childNodes.length; i < len; i++){
     if (element.childNodes[i].nodeType == 1){
         //執行某些操作
     }
 } 
 
 

 

這個例子會循環遍歷特定元素的每一個子節點,然後只在子節點的nodeType等於 1(表示是元素節點)的情況下,纔會執行某些操作。

如果想通過某個特定的標籤名取得子節點或後代節點該怎麼辦呢?實際上,元素也支持 getElementsByTagName()方法。在通過元素調用這個方法時,除了搜索起點是當前元素之外,其他方面都跟通過document調用這個方法相同,因此結果只會返回當前元素的後代。例如,要想取得前面<ul>元素中包含的所有<li>元素,可以使用下列代碼。

 var ul = document.getElementById("myList");
 var items = ul.getElementsByTagName("li");

 

要注意的是,這裏<ul>的後代中只包含直接子元素。不過,如果它包含更多層次的後代元素,那麼各個層次中包含的<li>元素也都會返回。

Text類型

文本節點由Text類型表示,包含的是可以照字面解釋的純文本內容。純文本中可以包含轉義後的

HTML 字符,但不能包含 HTML 代碼。Text節點具有以下特徵:

  1. nodeType的值爲 3;
  2. nodeName的值爲"#text";
  3. nodeValue的值爲節點所包含的文本;
  4. parentNode是一個Element;
  5. 不支持(沒有)子節點。

可以通過nodeValue屬性或data屬性訪問Text節點中包含的文本,這兩個屬性中包含的值相同。對nodeValue的修改也會通過data反映出來,反之亦然。使用下列方法可以操作節點中的文本。

  1. appendData(text):將text添加到節點的末尾。
  2. deleteData(offset, count):從offset指定的位置開始刪除count個字符。
  3. insertData(offset, text):在offset指定的位置插入text
  4. replaceData(offset, count, text):用text替換從offset指定的位置開始到offset+ count爲止處的文本。
  5. splitText(offset):從offset指定的位置將當前文本節點分成兩個文本節點。
  6. substringData(offset, count):提取從offset指定的位置開始到offset+count爲止處的字符串。

除了這些方法之外,文本節點還有一個 length 屬性,保存着節點中字符的數目。而且,

nodeValue.length和data.length中也保存着同樣的值。

在默認情況下,每個可以包含內容的元素 多只能有一個文本節點,而且必須確實有內容存在。來看幾個例子。

 

<!-- 沒有內容,也就沒有文本節點 -->
<div></div>

<!-- 有空格,因而有一個文本節點 -->
<div> </div>

<!-- 有內容,因而有一個文本節點 -->
<div>Hello World!</div>

 

上面代碼給出的第一個<div>元素沒有內容,因此也就不存在文本節點。開始與結束標籤之間只要存在內容,就會創建一個文本節點。因此,第二個<div>元素中雖然只包含一個空格,但仍然有一個文本子節點;文本節點的nodeValue值是一個空格。第三個<div>也有一個文本節點,其nodeValue的值爲"Hello World!"。可以使用以下代碼來訪問這些文本子節點。

var textNode = div.firstChild;     //或者div.childNodes[0]

在取得了文本節點的引用後,就可以像下面這樣來修改它了。

 


div.firstChild.nodeValue = "Some other message";

 如果這個文本節點當前存在於文檔樹中,那麼修改文本節點的結果就會立即得到反映。另外,在修 改文本節點時還要注意,此時的字符串會經過 HTML(或 XML,取決於文檔類型)編碼。換句話說, 小於號、大於號或引號都會像下面的例子一樣被轉義。 

//輸出結果是"Some &lt;strong&gt;other&lt;/strong&gt; message"
 div.firstChild.nodeValue = "Some <strong>other</strong> message"; 

應該說,這是在向 DOM文檔中插入文本之前,先對其進行 HTML編碼的一種有效方式。 

在IE8、Firefox、Safari、Chrome和Opera中,可以通過腳本訪問 Text 類型的構造 函數和原型。 

1. 創建文本節點

可以使用 document.createTextNode()創建新文本節點,這個方法接受一個參數——要插入節點中的文本。與設置已有文本節點的值一樣,作爲參數的文本也將按照 HTML 或 XML 的格式進行編碼。

var textNode = document.createTextNode("<strong>Hello</strong> world!");

在創建新文本節點的同時,也會爲其設置ownerDocument屬性。不過,除非把新節點添加到文檔樹中已經存在的節點中,否則我們不會在瀏覽器窗口中看到新節點。下面的代碼會創建一個<div>元素並向其中添加一條消息。

var element = document.createElement("div");
 element.className = "message";



var textNode = document.createTextNode("Hello world!");

element.appendChild(textNode);                                                   
document.body.appendChild(element);

 

這個例子創建了一個新<div>元素併爲它指定了值爲"message"的 class 特性。然後,又創建了一個文本節點,並將其添加到前面創建的元素中。 後一步,就是將這個元素添加到了文檔的<body> 元素中,這樣就可以在瀏覽器中看到新創建的元素和文本節點了。一般情況下,每個元素只有一個文本子節點。不過,在某些情況下也可能包含多個文本子節點,如下面的例子所示。

 

 var element = document.createElement("div");
 element.className = "message";

 var textNode = document.createTextNode("Hello world!");
 element.appendChild(textNode);

 

var anotherTextNode = document.createTextNode("Yippee!"); 
element.appendChild(anotherTextNode);

 

document.body.appendChild(element);

如果兩個文本節點是相鄰的同胞節點,那麼這兩個節點中的文本就會連起來顯示,中間不會有空格。

2. 規範化文本節點

DOM 文檔中存在相鄰的同胞文本節點很容易導致混亂,因爲分不清哪個文本節點表示哪個字符串。另外,DOM 文檔中出現相鄰文本節點的情況也不在少數,於是就催生了一個能夠將相鄰文本節點合併的方法。這個方法是由Node類型定義的(因而在所有節點類型中都存在),名叫normalize()。如果在一個包含兩個或多個文本節點的父元素上調用 normalize()方法,則會將所有文本節點合併成一個節點,結果節點的nodeValue 等於將合併前每個文本節點的nodeValue 值拼接起來的值。來看一個例子。

 var element = document.createElement("div");
 element.className = "message";

 var textNode = document.createTextNode("Hello world!");
 element.appendChild(textNode);

 var anotherTextNode = document.createTextNode("Yippee!"); 
element.appendChild(anotherTextNode);

 document.body.appendChild(element);


 alert(element.childNodes.length);    //2


 element.normalize(); alert(element.childNodes.length);    //1

alert(element.firstChild.nodeValue);    // "Hello world!Yippee!"  

瀏覽器在解析文檔時永遠不會創建相鄰的文本節點。這種情況只會作爲執行 DOM操作的結果出現。

在某些情況下,執行normalize()方法會導致 IE6 崩潰。不過,在 IE6 後來的補丁中,可能已經修復了這個問題(未經證實)。IE7 及更高版本中不存在這個問題。

3. 分割文本節點

Text類型提供了一個作用與normalize()相反的方法:splitText()。這個方法會將一個文本節點分成兩個文本節點,即按照指定的位置分割nodeValue值。原來的文本節點將包含從開始到指定位置之前的內容,新文本節點將包含剩下的文本。這個方法會返回一個新文本節點,該節點與原節點的 parentNode相同。來看下面的例子。

var element = document.createElement("div");
 element.className = "message";
var textNode = document.createTextNode("Hello world!");
 element.appendChild(textNode);

document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);
 alert(element.firstChild.nodeValue);  
  //"Hello"
 alert(newNode.nodeValue);             //" world!" 
alert(element.childNodes.length);       //2 

 

 

在這個例子中,包含"Hello world!"的文本節點被分割爲兩個文本節點,從位置 5 開始。位置 5 是"Hello"和"world!"之間的空格,因此原來的文本節點將包含字符串"Hello",而新文本節點將包含文本"world!"(包含空格)。分割文本節點是從文本節點中提取數據的一種常用 DOM 解析技術。

Comment類型

註釋在 DOM中是通過 Comment 類型來表示的。Comment 節點具有下列特徵: 

  • nodeType 的值爲 8;
  • nodeName 的值爲"#comment";
  • nodeValue 的值是註釋的內容;
  • parentNode 可能是 Document 或 Element;
  • 不支持(沒有)子節點。

Comment 類型與 Text 類型繼承自相同的基類,因此它擁有除 splitText()之外的所有字符串操 作方法。與 Text 類型相似,也可以通過 nodeValue 或 data 屬性來取得註釋的內容。 

註釋節點可以通過其父節點來訪問,以下面的代碼爲例。 
 

<div id="myDiv"><!--A comment --></div> 


 在此,註釋節點是<div>元素的一個子節點,因此可以通過下面的代碼來訪問它。 
 

var div = document.getElementById("myDiv");
 var comment = div.firstChild; alert(comment.data);
    //"A comment" 

另外,使用 document.createComment()併爲其傳遞註釋文本也可以創建註釋節點,如下面的例 子所示。 


 var comment = document.createComment("A comment "); 

顯然,開發人員很少會創建和訪問註釋節點,因爲註釋節點對算法鮮有影響。此外,瀏覽器也不會 識別位於</html>

在 Firefox、Safari、Chrome和 Opera中,可以訪問 Comment 類型的構造函數和 原型。在 IE8 中,註釋節點被視作標籤名爲"!"的元素。也就是說,使用 getElementsByTagName()可以取得註釋節點。儘管 IE9 沒有把註釋當成元素,但 它仍然通過一個名爲 HTMLCommentElement 的構造函數來表示註釋。 


 

 

CDATASection類型 

CDATASection 類型只針對基於 XML 的文檔,表示的是 CDATA 區域。與 Comment 類似, CDATASection 類型繼承自 Text 類型,因此擁有除 splitText()之外的所有字符串操作方法。 CDATASection 節點具有下列特徵: 

  • nodeType 的值爲 4;
  • nodeName 的值爲"#cdata-section";
  • nodeValue 的值是 CDATA區域中的內容;
  • parentNode 可能是 Document 或 Element;
  • 不支持(沒有)子節點。 

CDATA區域只會出現在 XML文檔中,因此多數瀏覽器都會把 CDATA區域錯誤地解析爲 Comment 或 Element。以下面的代碼爲例: 

<div id="myDiv"><![CDATA[This is some content.]]></div> 

這個例子中的<div>元素應該包含一個 CDATASection 節點。可是,四大主流瀏覽器無一能夠這樣 解析它。即使對於有效的 XHTML頁面,瀏覽器也沒有正確地支持嵌入的 CDATA區域。 在真正的 XML文檔中,可以使用 document.createCDataSection()來創建 CDATA區域,只需 爲其傳入節點的內容即可

在 Firefox、Safari、Chrome和 Opera中,可以訪問 CDATASection 類型的構造函 數和原型。IE9及之前版本不支持這個類型。 

 

 

 

DocumentType類型 

DocumentType 類型在 Web 瀏覽器中並不常用,僅有 Firefox、Safari和 Opera支持它①

DocumentFragment類型 

在所有節點類型中,只有 DocumentFragment 在文檔中沒有對應的標記。DOM 規定文檔片段 (document fragment)是一種“輕量級”的文檔,可以包含和控制節點,但不會像完整的文檔那樣佔用 額外的資源。DocumentFragment 節點具有下列特徵: 

  •  nodeType 的值爲 11;
  • nodeName 的值爲"#document-fragment";
  • nodeValue 的值爲 null;
  • parentNode 的值爲 null;
  • 子節點可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或 EntityReference。 

雖然不能把文檔片段直接添加到文檔中,但可以將它作爲一個“倉庫”來使用,即可以在裏面保存將 來可能會添加到文檔中的節點。要創建文檔片段,可以使用 document.createDocumentFragment()方 法,如下所示: 

var fragment = document.createDocumentFragment(); 

文檔片段繼承了 Node 的所有方法,通常用於執行那些針對文檔的 DOM操作。如果將文檔中的節 點添加到文檔片段中,就會從文檔樹中移除該節點,也不會從瀏覽器中再看到該節點。添加到文檔片段 中的新節點同樣也不屬於文檔樹。可以通過 appendChild()或 insertBefore()將文檔片段中內容添 加到文檔中。在將文檔片段作爲參數傳遞給這兩個方法時,實際上只會將文檔片段的所有子節點添加到 相應位置上;文檔片段本身永遠不會成爲文檔樹的一部分。來看下面的 HTML示例代碼: 

<ul id="myList"></ul> 

假設我們想爲這個<ul>元素添加 3個列表項。如果逐個地添加列表項,將會導致瀏覽器反覆渲染(呈 現)新信息。爲避免這個問題,可以像下面這樣使用一個文檔片段來保存創建的列表項,然後再一次性 將它們添加到文檔中。 

var fragment = document.createDocumentFragment();
 var ul = document.getElementById("myList");
 var li = null; 
 
for (var i=0; i < 3; i++){
     li = document.createElement("li");
     li.appendChild(document.createTextNode("Item " + (i+1)));
     fragment.appendChild(li);
 } 
 
ul.appendChild(fragment); 

在這個例子中,我們先創建一個文檔片段並取得了對<ul>元素的引用。然後,通過 for 循環創建 3個列表項,並通過文本表示它們的順序。爲此,需要分別創建<li>元素、創建文本節點,再把文本節 點添加到<li>元素。接着使用 appendChild()將<li>元素添加到文檔片段中。循環結束後,再調用 appendChild()並傳入文檔片段,將所有列表項添加到<ul>元素中。此時,文檔片段的所有子節點都 被刪除並轉移到了<ul>元素中。 

 

Attr類型 

.元素的特性在 DOM中以 Attr 類型來表示。在所有瀏覽器中(包括 IE8),都可以訪問 Attr 類型 的構造函數和原型。從技術角度講,特性就是存在於元素的 attributes 屬性中的節點。特性節點具有 下列特徵: 

  •  nodeType 的值爲 2;
  •  nodeName 的值是特性的名稱;
  •  nodeValue 的值是特性的值;
  •  parentNode 的值爲 null;
  •  在 HTML中不支持(沒有)子節點; 
  • 在 XML中子節點可以是 Text 或 EntityReference。 

儘管它們也是節點,但特性卻不被認爲是 DOM 文檔樹的一部分。開發人員常使用的是 getAt- tribute()、setAttribute()和 remveAttribute()方法,很少直接引用特性節點。 

Attr 對象有 3個屬性:name、value 和 specified。其中,name 是特性名稱(與 nodeName 的 值相同),value 是特性的值(與 nodeValue 的值相同),而 specified 是一個布爾值,用以區別特 性是在代碼中指定的,還是默認的

使用 document.createAttribute()並傳入特性的名稱可以創建新的特性節點。例如,要爲元素 添加 align 特性,可以使用下列代碼: 

var attr = document.createAttribute("align");
 attr.value = "left";
 element.setAttributeNode(attr);
 alert(element.attributes["align"].value); 
      //"left"
 alert(element.getAttributeNode("align").value);
 //"left" 
alert(element.getAttribute("align"));           //"left" 

這個例子創建了一個新的特性節點。由於在調用 createAttribute()時已經爲 name 屬性賦了值, 所以後面就不必給它賦值了。之後,又把 value 屬性的值設置爲"left"。爲了將新創建的特性添加到 元素中,必須使用元素的 setAttributeNode()方法。添加特性之後,可以通過下列任何方式訪問該 特性:attributes 屬性、getAttributeNode()方法以及 getAttribute()方法。其中,attributes 和 getAttributeNode()都會返回對應特性的 Attr 節點,而 getAttribute()則只返回特性的值。 

我們並不建議直接訪問特性節點。實際上,使用 getAttribute()、setAttribute() 和 removeAttribute()方法遠比操作特性節點更爲方便。 

 

 

 

DOM操作技術

很多時候,DOM操作都比較簡明,因此用 JavaScript 生成那些通常原本是用 HTML 代碼生成的內 容並不麻煩。不過,也有一些時候,操作 DOM並不像表面上看起來那麼簡單。由於瀏覽器中充斥着隱 藏的陷阱和不兼容問題,用 JavaScript代碼處理 DOM的某些部分要比處理其他部分更復雜一些。 

動態腳本 

跟操作 HTML元素一樣,創建動態腳本也有兩種方 式:插入外部文件和直接插入 JavaScript代碼。 

動態加載的外部 JavaScript文件能夠立即運行,比如下面的<script>元素: .

.

<script type="text/javascript" src="client.js"></script> 

這個<script>元素包含了第 9章的客戶端檢測腳本。而創建這個節點的 DOM代碼如下所示: 

var script = document.createElement("script");
 script.type = "text/javascript"; 
script.src = "client.js"; 
document.body.appendChild(script); 

顯然,這裏的 DOM代碼如實反映了相應的 HTML代碼。不過,在執行後一行代碼把<script> 元素添加到頁面中之前,是不會下載外部文件的。也可以把這個元素添加到<head>元素中,效果相同。 整個過程可以使用下面的函數來封裝: 

function loadScript(url){
     var script = document.createElement("script");
     script.type = "text/javascript";
     script.src = url;
     document.body.appendChild(script);
 } 
 

 然後,就可以通過調用這個函數來加載外部的 JavaScript文件了: 

loadScript("client.js"); 

加載完成後,就可以在頁面中的其他地方使用這個腳本了。問題只有一個:怎麼知道腳本加載完成 呢?遺憾的是,並沒有什麼標準方式來探知這一點。不過,與此相關的一些事件倒是可以派上用場,但 要取決於所用的瀏覽器,詳細討論請見第 13章。 另一種指定 JavaScript代碼的方式是行內方式,如下面的例子所示:

<script type="text/javascript">
     function sayHi(){
         alert("hi");
     }
 </script> 
 

從邏輯上講,下面的 DOM代碼是有效的

var script = document.createElement("script");
 script.type = "text/javascript";
 script.appendChild(document.createTextNode("function sayHi(){alert('hi');}"));
 document.body.appendChild(script); 

在 Firefox、Safari、Chrome和 Opera中,這些 DOM代碼可以正常運行。但在 IE中,則會導致錯誤。 IE 將<script>視爲一個特殊的元素,不允許 DOM 訪問其子節點。不過,可以使用<script>元素的 text 屬性來指定 JavaScript代碼,像下面的例子這樣: 

var script = document.createElement("script");
 script.type = "text/javascript";
 script.text = "function sayHi(){alert('hi');}";
 document.body.appendChild(script); 

經過這樣修改之後的代碼可以在 IE、Firefox、Opera和 Safari 3及之後版本中運行。Safari 3.0之前 的版本雖然不能正確地支持 text 屬性,但卻允許使用文本節點技術來指定代碼。如果需要兼容早期版 本的 Safari,可以使用下列代碼: 

var script = document.createElement("script");
 script.type = "text/javascript";
 var code = "function sayHi(){alert('hi');}"; 
try {
     script.appendChild(document.createTextNode("code"));
 } catch (ex){
     script.text = "code";
 } 
document.body.appendChild(script); 

這裏,首先嚐試標準的 DOM文本節點方法,因爲除了 IE(在 IE中會導致拋出錯誤),所有瀏覽器 都支持這種方式。如果這行代碼拋出了錯誤,那麼說明是 IE,於是就必須使用 text 屬性了。整個過程 可以用以下函數來表示: 

function loadScriptString(code){
     var script = document.createElement("script");
     script.type = "text/javascript";
     try {
         script.appendChild(document.createTextNode(code));
     } catch (ex){
         script.text = code;
     }
     document.body.appendChild(script);
 } 
 

下面是調用這個函數的示例: 

loadScriptString("function sayHi(){alert('hi');}"); 

 以這種方式加載的代碼會在全局作用域中執行,而且當腳本執行後將立即可用。實際上,這樣執行 代碼與在全局作用域中把相同的字符串傳遞給 eval()是一樣的。 

 

 

動態樣式 

能夠把 CSS樣式包含到 HTML頁面中的元素有兩個。其中,<link>元素用於包含來自外部的文件, 而<style>元素用於指定嵌入的樣式。與動態腳本類似,所謂動態樣式是指在頁面剛加載時不存在的樣 式;動態樣式是在頁面加載完成後動態添加到頁面中的。 我們以下面這個典型的<link>元素爲例: 
 

<link rel="stylesheet" type="text/css" href="styles.css"> 

使用 DOM代碼可以很容易地動態創建出這個元素: 

var link = document.createElement("link");
 link.rel = "stylesheet";
 link.type = "text/css";
 link.href = "style.css";
 var head = document.getElementsByTagName("head")[0];
 head.appendChild(link); 

以上代碼在所有主流瀏覽器中都可以正常運行。需要注意的是,必須將<link>元素添加到<head> 而不是<body>元素,才能保證在所有瀏覽器中的行爲一致。整個過程可以用以下函數來表示: 
 

function loadStyles(url){
     var link = document.createElement("link"); 

    link.rel = "stylesheet";
     link.type = "text/css"; 
    link.href = url;
     var head = document.getElementsByTagName("head")[0];
     head.appendChild(link);
 } 
 

調用 loadStyles()函數的代碼如下所示: 
 

loadStyles("styles.css"); 
 

加載外部樣式文件的過程是異步的,也就是加載樣式與執行 JavaScript代碼的過程沒有固定的次序。 一般來說,知不知道樣式已經加載完成並不重要;不過,也存在幾種利用事件來檢測這個過程是否完成 的技術,這些技術將在第 13章討論。 另一種定義樣式的方式是使用<style>元素來包含嵌入式 CSS,如下所示: 

<style type="text/css">
 body {
     background-color: red;
 }
 </style> 

按照相同的邏輯,下列 DOM代碼應該是有效的: 

var style = document.createElement("style");
 style.type = "text/css";
 style.appendChild(document.createTextNode("body{background-color:red}"));
  var head = document.getElementsByTagName("head")[0];
 head.appendChild(style); 

以上代碼可以在 Firefox、Safari、Chrome和 Opera中運行,在 IE中則會報錯。IE將<style>視爲 一個特殊的、與<script>類似的節點,不允許訪問其子節點。事實上,IE此時拋出的錯誤與向<script> 元素添加子節點時拋出的錯誤相同。解決 IE中這個問題的辦法,就是訪問元素的 styleSheet 屬性, 該屬性又有一個 cssText 屬性,可以接受 CSS代碼(第 13章將進一步討論這兩個屬性),如下面的例 子所示。 

var style = document.createElement("style");
 style.type = "text/css";
 try{
     style.appendChild(document.createTextNode("body{background-color:red}"));
 } catch (ex){
     style.styleSheet.cssText = "body{background-color:red}";
 }
 var head = document.getElementsByTagName("head")[0];

 head.appendChild(style); 
 

與動態添加嵌入式腳本類似,重寫後的代碼使用了 try-catch 語句來捕獲 IE拋出的錯誤,然後再 使用針對 IE的特殊方式來設置樣式。因此,通用的解決方案如下。 

function loadStyleString(css){
     var style = document.createElement("style"); 
     style.type = "text/css";
     try{
         style.appendChild(document.createTextNode(css));
     } catch (ex){
         style.styleSheet.cssText = css;
     } 
    var head = document.getElementsByTagName("head")[0];
     head.appendChild(style);
 } 
 

調用這個函數的示例如下

loadStyleString("body{background-color:red}"); 

這種方式會實時地向頁面中添加樣式,因此能夠馬上看到變化

如果專門針對 IE編寫代碼,務必小心使用 styleSheet.cssText 屬性。在重用 同一個<style>元素並再次設置這個屬性時,有可能會導致瀏覽器崩潰。同樣,將 cssText 屬性設置爲空字符串也可能導致瀏覽器崩潰。我們希望 IE中的這個 bug能 夠在將來被修復。 

 

操作表格 

<table>元素是 HTML中複雜的結構之一。要想創建表格,一般都必須涉及表示表格行、單元格、 表頭等方面的標籤。由於涉及的標籤多,因而使用核心 DOM方法創建和修改表格往往都免不了要編寫 大量的代碼。假設我們要使用 DOM來創建下面的 HTML表格。 

<table border="1" width="100%">
     <tbody>
         <tr>
             <td>Cell 1,1</td>
             <td>Cell 2,1</td>
         </tr>
         <tr> 
            <td>Cell 1,2</td>
             <td>Cell 2,2</td>
         </tr>
     </tbody>
 </table> 
 

要使用核心 DOM方法創建這些元素,得需要像下面這麼多的代碼: 

//創建
 table var table = document.createElement("table");
 table.border = 1; table.width = "100%"; 

 
//創建
 tbody var tbody = document.createElement("tbody"); 
table.appendChild(tbody); 
 
//創建第一行
 var row1 = document.createElement("tr");
 tbody.appendChild(row1);
 var cell1_1 = document.createElement("td"); 
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
 row1.appendChild(cell1_1);
 var cell2_1 = document.createElement("td");
 cell2_1.appendChild(document.createTextNode("Cell 2,1"));
 row1.appendChild(cell2_1); 
 
//創建第二行
 var row2 = document.createElement("tr");

 tbody.appendChild(row2);
 var cell1_2 = document.createElement("td"); cell1_2.appendChild(document.createTextNode("Cell 1,2"));
 row2.appendChild(cell1_2);
 var cell2_2= document.createElement("td"); cell2_2.appendChild(document.createTextNode("Cell 2,2"));

 row2.appendChild(cell2_2); 
 
//將表格添加到文檔主體中
 document.body.appendChild(table); 

顯然,DOM代碼很長,還有點不太好懂。爲了方便構建表格,HTML DOM還爲<table>、<tbody> 和<tr>元素添加了一些屬性和方法。 

爲<table>元素添加的屬性和方法如下。 

  •     caption:保存着對<caption>元素(如果有)的指針。
  •  tBodies:是一個<tbody>元素的 HTMLCollection。
  •  tFoot:保存着對<tfoot>元素(如果有)的指針。
  •  tHead:保存着對<thead>元素(如果有)的指針。
  •  rows:是一個表格中所有行的 HTMLCollection。
  •  createTHead():創建<thead>元素,將其放到表格中,返回引用。
  •  createTFoot():創建<tfoot>元素,將其放到表格中,返回引用。
  •  createCaption():創建<caption>元素,將其放到表格中,返回引用。
  •  deleteTHead():刪除<thead>元素。 
  •      deleteTFoot():刪除<tfoot>元素。
  •  deleteCaption():刪除<caption>元素。
  •  deleteRow(pos):刪除指定位置的行。
  •  insertRow(pos):向 rows 集合中的指定位置插入一行。 爲<tbody>元素添加的屬性和方法如下。
  •  rows:保存着<tbody>元素中行的 HTMLCollection。
  •  deleteRow(pos):刪除指定位置的行。
  •  insertRow(pos):向 rows 集合中的指定位置插入一行,返回對新插入行的引用。 

爲<tr>元素添加的屬性和方法如下。 

  •  cells:保存着<tr>元素中單元格的 HTMLCollection。
  •  deleteCell(pos):刪除指定位置的單元格。
  •  insertCell(pos):向 cells 集合中的指定位置插入一個單元格,返回對新插入單元格的引用

使用這些屬性和方法,可以極大地減少創建表格所需的代碼數量。例如,使用這些屬性和方法可以 將前面的代碼重寫如下(加陰影的部分是重寫後的代碼)。 

//創建 table
 var table = document.createElement("table");
 table.border = 1; table.width = "100%"; 
 
//創建tbody 
var tbody = document.createElement("tbody");
 table.appendChild(tbody); 
 
//創建第一行
 tbody.insertRow(0); tbody.rows[0].insertCell(0);
 tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));

 tbody.rows[0].insertCell(1);
 tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1")); 
 
//創建第二行 tbody.insertRow(1); 
tbody.rows[1].insertCell(0);
 tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
 tbody.rows[1].insertCell(1); tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));
 
 
//將表格添加到文檔主體中
onload = function ()
{
 document.body.appendChild(table);
}

在這次的代碼中,創建<table>和<tbody>的代碼沒有變化。不同的是創建兩行的部分,其中使用 了 HTML DOM 定義的表格屬性和方法。在創建第一行時,通過<tbody>元素調用了 insertRow()方 法,傳入了參數 0——表示應該將插入的行放在什麼位置上。執行這一行代碼後,就會自動創建一行並 將其插入到<tbody>元素的位置 0上,因此就可以馬上通過 tbody.rows[0]來引用新插入的行。

創建單元格的方式也十分相似,即通過<tr>元素調用 insertCell()方法並傳入放置單元格的位 置。然後,就可以通過 tbody.rows[0].cells[0]來引用新插入的單元格,因爲新創建的單元格被插 入到了這一行的位置 0上。 總之,使用這些屬性和方法創建表格的邏輯性更強,也更容易看懂,儘管技術上這兩套代碼都是正 確的。

 

 

 

 

 

 

 

 

 

使用NodeList 

理解 NodeList 及其“近親”NamedNodeMap 和 HTMLCollection,是從整體上透徹理解 DOM的 關鍵所在。這三個集合都是“動態的”;換句話說,每當文檔結構發生變化時,它們都會得到更新。因 此,它們始終都會保存着新、準確的信息。從本質上說,所有 NodeList 對象都是在訪問 DOM文 檔時實時運行的查詢。例如,下列代碼會導致無限循環: 

var divs = document.getElementsByTagName("div"),
     i,
      div; 
 
for (i=0; i < divs.length; i++){
     div = document.createElement("div");
     document.body.appendChild(div);
 }

 第一行代碼會取得文檔中所有<div>元素的 HTMLCollection。

由於這個集合是“動態的”,因此 只要有新<div>元素被添加到頁面中,這個元素也會被添加到該集合中。瀏覽器不會將創建的所有集 合都保存在一個列表中,而是在下一次訪問集合時再更新集合。結果,在遇到上例中所示的循環代碼 時,就會導致一個有趣的問題。每次循環都要對條件 i < divs.length 求值,意味着會運行取得所 有<div>元素的查詢。

考慮到循環體每次都會創建一個新<div>元素並將其添加到文檔中,因此 divs.length 的值在每次循環後都會遞增。既然 i 和 divs.length 每次都會同時遞增,結果它們的 值永遠也不會相等。 如果想要迭代一個 NodeList,好是使用 length 屬性初始化第二個變量,然後將迭代器與該變 量進行比較,如下面的例子所示: 

var divs = document.getElementsByTagName("div"),
     i,
     len,
     div; 
 
for (i=0, len=divs.length; i < len; i++){ 
    div = document.createElement("div");
     document.body.appendChild(div);
 } 
 

這個例子中初始化了第二個變量 len。由於 len 中保存着對 divs.length 在循環開始時的一個快 照,因此就會避免上一個例子中出現的無限循環問題。在本章演示迭代 NodeList 對象的例子中,使用 的都是這種更爲保險的方式。 一般來說,應該儘量減少訪問 NodeList 的次數。因爲每次訪問 NodeList,都會運行一次基於文 檔的查詢。所以,可以考慮將從 NodeList 中取得的值緩存起來。 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章