1. 程式人生 > >DOM(文件物件模型)

DOM(文件物件模型)

1、節點層次

  • Node型別

    • DOM1 級定義了一個 Node 介面,該介面將由 DOM 中的所有節點型別實現,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 == 1){ //適用於所有瀏覽器
          alert("Node is an element.");
      } 
    • nodeName 和 nodeValue 屬性:這兩個屬性的值完全取決於節點的型別

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

      • 每個節點都有一個 childNodes 屬性,其中儲存著一個 NodeList 物件。NodeList 是一種類陣列物件,用於儲存一組有序的節點,可以通過位置來訪問這些節點

        var firstChild = someNode.childNodes[0];//通過方括號訪問
        var secondChild = someNode.childNodes.item(1);//使用 item()方法訪問
        var count = someNode.childNodes.length; //這個物件也有 length 屬性(訪問那一刻)
        
        //以將 NodeList 物件轉換為陣列:相容IE8及之前瀏覽器
        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;
        } 
      • 每個節點都有一個 parentNode 屬性,該屬性指向文件樹中的父節點,包含在 childNodes 列表中
        的所有節點都具有相同的父節點,因此它們的 parentNode 屬性都指向同一個節點;包含在childNodes 列表中的每個節點相互之間都是同胞節點。通過使用列表中每個節點的 previousSibling
        和 nextSibling 屬性,可以訪問同一列表中的其他節點。列表中第一個節點的 previousSibling 屬性
        值為 null,而列表中最後一個節點的 nextSibling 屬性的值同樣也為 null;如果列表中只有一個節點,那麼該節點的 nextSibling 和 previousSibling 都為 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.");
        } 
      • 父節點與其第一個和最後一個子節點之間也存在特殊關係。父節點的 firstChild 和 lastChild屬性分別指向其 childNodes 列表中的第一個和最後一個節點;在只有一個子節點的情況下,firstChild 和
        lastChild 指向同一個節點;如果沒有子節點,那麼 firstChild 和 lastChild 的值均為 null

        someNode.firstChild === someNode.childNodes[0]
        someNode.lastChild === childNodes [someNode.childNodes.length-1]
      • hasChildNodes()也是在節點包含一或多個子節點的情況下返回 true

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

    • 操作節點:

      • appendChild()方法:用於向 childNodes 列表的末尾新增一個節點,返回新增的節點

        //新增節點後,childNodes 的新增節點、父節點及以前的最後一個子節點的關係指標都會相應地得到更新
        var returnedNode = someNode.appendChild(newNode);
        alert(returnedNode == newNode); //true
        alert(someNode.lastChild == newNode); //true
        
        /*如果傳入到 appendChild()中的節點已經是文件的一部分了,那結果就是將該節點從原來的位置
        轉移到新位置*/
        //someNode 有多個子節點
        var returnedNode = someNode.appendChild(someNode.firstChild);
        alert(returnedNode == someNode.firstChild); //false
        alert(returnedNode == someNode.lastChild); //true 
      • insertBefore()方法:把節點放在 childNodes 列表中某個特定的位置上。這個方法接受兩個引數:要插入的節點和作為參照的節點。插入節點後,被插入的節點會變成參照節點的前一個同胞節點(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 
      • replaceChild()方法:接受的兩個引數是是要插入的節點和要替換的節點,要替換的節點將由這個
        方法返回並從文件樹中被移除,同時由要插入的節點佔據其位置

        //替換第一個子節點
        var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
        //替換最後一個子節點
        returnedNode = someNode.replaceChild(newNode, someNode.lastChild); 
      • removeChild()方法:移除節點,接受一個引數,即要移除的節點。被移除的節點將成為方法的返回值

        //移除第一個子節點
        var formerFirstChild = someNode.removeChild(someNode.firstChild);
        //移除最後一個子節點
        var formerLastChild = someNode.removeChild(someNode.lastChild);
      • 注意:要使用這幾個方法必須先取得父節點(使用 parentNode 屬性)。另外,並不是所有型別的節點都有子節點,如果在不支援子節點的節點上呼叫了這些方法,將會導致錯誤發生

    • 其他方法

      • cloneNode()方法:用於建立呼叫這個方法的節點的一個完全相同的副本。cloneNode()方法接受一個布林值引數,表示是否執行深複製;複製後返回的節點副本屬於文件所有,但並沒有為它指定父節點,所以需要通過 appendChild()、insertBefore()或 replaceChild()將它新增到文件中

        <ul>
         <li>item 1</li>
         <li>item 2</li>
         <li>item 3</li>
        </ul> 
        <script>
            //已經將<ul>元素的引用儲存在了變數 myList 中
          var deepList = myList.cloneNode(true);
          alert(deepList.childNodes.length); //3(IE < 9)或 7(其他瀏覽器)
          var shallowList = myList.cloneNode(false);
          alert(shallowList.childNodes.length); //0 
        </script>
      • normalize()方法:處理文件樹中的文字節點,由於解析器的實現或 DOM 操作等原因,可能會出現文字節點不包含文字,或者接連出現兩個文字節點的情況。當在某個節點上呼叫這個方法時,就會在該節點的後代節點中查詢上述兩種情況。如果找到了空文字節點,則刪除它;如果找到相鄰的文字節點,則將它們合併為一個文字節點

  • Document型別

    • 概念:JavaScript 通過 Document 型別表示文件。在瀏覽器中,document 物件是 HTMLDocument(繼承自 Document 型別)的一個例項,表示整個 HTML 頁面。而且,document 物件是 window 物件的一個屬性,因此可以將其作為全域性物件來訪問。

    • 特徵:

      • nodeType 的值為 9
      • nodeName 的值為"#document"
      • nodeValue 的值為 null
      • parentNode 的值為 null
      • ownerDocument 的值為 null
      • 其子節點可能是一個 DocumentType(最多一個)、Element(最多一個)、ProcessingInstruction
        或 Comment
    • 文件的子節點:

      • 訪問Document 節點的子節點的快捷方式:第一個就是documentElement屬性,該屬性始終指向 HTML 頁面中的<html>元素;另一個就是通過 childNodes 列表訪問文件元素

        <html>
          <body>
        
          </body>
        </html> 
        <script>
          var html = document.documentElement; //取得對<html>的引用
          alert(html === document.childNodes[0]); //true
          alert(html === document.firstChild); //true
        </script>
      • document 物件還有一個 body 屬性,直接指向<body>元素

        var body = document.body; //取得對<body>的引用
      • Document 另一個可能的子節點是 DocumentType,通常將<!DOCTYPE>標籤看成一個與文件其他
        部分不同的實體,部分不同的實體,可以通過 doctype 屬性來訪問它的資訊

        var doctype = document.doctype; //取得對<!DOCTYPE>的引用
      • 出現在元素外部的註釋應該算是文件的子節點,但是不同的瀏覽器在是否解析這些註釋以及能否正確處理它們等方面,也存在很大差異

    • 文件資訊

      • title屬性:包含著<title>元素中的文字——顯示在瀏覽器視窗的標題欄或標籤頁上;通過這個屬性可以取得當前頁面的標題,也可以修改當前頁面的標題並反映在瀏覽器的標題欄中。修改 title 屬性的值不會改變

        //取得文件標題
        var originalTitle = document.title;
        //設定文件標題
        document.title = "New page title"; 
      • URL 屬性:包含頁面完整的 URL(即位址列中顯示的 URL),不可以設定

        //取得完整的 URL
        var url = document.URL;
      • domain 屬性:只包含頁面的域名。可以設定,但由於安全方面的限制,也並非可以給 domain 設
        置任何值

        //取得域名
        var domain = document.domain; 
        
        //假設頁面來自 p2p.wrox.com 域
        document.domain = "wrox.com"; // 成功
        document.domain = "nczonline.net"; // 出錯!
        
        //當頁面中包含來自其他子域的框架或內嵌框架時,能夠設定 document.domain 就非常方便了。由於跨域安全限制,來自不同子域的頁面無法通過 JavaScript 通訊。而通過將每個頁面的document.domain 設定為相同的值,這些頁面就可以互相訪問對方包含的 JavaScript 物件了
        
        //如果域名一開始是“鬆散的”(loose),那麼不能將它再設定為“緊繃的”(tight)
        //假設頁面來自於 p2p.wrox.com 域
        document.domain = "wrox.com"; //鬆散的(成功)
        document.domain = "p2p.wrox.com"; //緊繃的(出錯!)
      • referrer屬性:儲存著連結到當前頁面的那個頁面的 URL,不可以設定

        //取得來源頁面的 URL
        var referrer = document.referrer; 
    • 查詢元素

      • getElementById()方法,接收一個引數:要取得的元素的 ID。如果找到相應的元素則返回該元素,如果不存在帶有相應 ID 的元素,則返回 null

        <div id="myDiv">Some text</div>
        <script>
          //可以使用下面的程式碼取得這個元素:
          var div = document.getElementById("myDiv"); //取得<div>元素的引用
        </script>
      • getElementsByTagName()方法,接受一個引數,即要取得元素的標籤名,而返回的是包含零或多個元素的 NodeList,這個方法會返回一個 HTMLCollection 物件,作為一個“動態”集合

        //與 NodeList 物件類似的方法
        var images = document.getElementsByTagName("img");
        alert(images.length); //輸出影象的數量
        alert(images[0].src); //輸出第一個影象元素的 src 特性
        alert(images.item(0).src); //輸出第一個影象元素的 src 特性
        
        //HTMLCollection 物件的方法
        //<img src="myimage.gif" name="myImage"> 
        var myImage = images.namedItem("myImage"); //namedItem
        var myImage = images["myImage"]; //按名稱訪問項
        
        //取得文件中的所有元素
        var allElements = document.getElementsByTagName("*");
      • getElementsByName()方法,只有 HTMLDocument 型別才有的方法,會返回帶有給定 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>
        <script>
            ///返回一個 HTMLCollectioin
          var radios = document.getElementsByName("color"); /
        </script>
    • 特殊集合:這些集合都是 HTMLCollection 物件,為訪問文件常用的部分提供了快捷方式

      • document.anchors,包含文件中所有帶 name 特性的元素;
      • document.applets,包含文件中所有的<applet>元素,不再推薦使用了
      • document.forms,包含文件中所有的<form>元素
      • document.images,包含文件中所有的<img>元素
      • document.links,包含文件中所有帶 href 特性的<a>元素
    • DOM 一致性檢測

      • 由於 DOM 分為多個級別,也包含多個部分,因此檢測瀏覽器實現了 DOM 的哪些部分就十分必要

      • document.implementation 屬性就是為此提供相應資訊和功能的物件,與瀏覽器對 DOM 的實現
        直接對應

      • DOM1 級只為 document.implementation 規定了一個方法,即 hasFeature()方法,接受兩個引數:要檢測的 DOM 功能的名稱及版本號。如果瀏覽器支援給定名稱和版本的功能,則該方法返回 true

        var hasXmlDom = document.implementation.hasFeature("XML", "1.0");
      • 建議多數情況下,在使用 DOM 的某些特殊的功能之前,最好除了檢測hasFeature()之外,還同時使用能力檢測

    • 文件寫入

      • write()方法,接受一個字串引數,即要寫入到輸出流中的文字,原樣寫入

        <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> 
      • writeln()方法,接受一個字串引數,即要寫入到輸出流中的文字,在字串的末尾新增換行符

      • open()和 close()分別用於開啟和關閉網頁的輸出流,如果是在頁面載入期間使用 write()
        或 writeln()方法,則不需要用到這兩個方法

  • Element型別

    • 概念:用於表現 XML 或 HTML元素,提供了對元素標籤名、子節點及特性的訪問

    • 特徵:

      • nodeType 的值為 1;
      • nodeName 的值為元素的標籤名;
      • nodeValue 的值為 null;
      • parentNode 可能是 Document 或 Element;
      • 其子節點可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或EntityReference
      //訪問元素的標籤名,可以使用 nodeName 屬性,也可以使用 tagName 屬性
      var div = document.getElementById("myDiv");
      alert(div.tagName); //"DIV"
      alert(div.tagName == div.nodeName); //true 
      
      //這種做法適用於 HTML 文件,也適用於 XML 文件
      if (element.tagName.toLowerCase() == "div"){ //這樣最好(適用於任何文件)
          //在此執行某些操作
      }
    • HTML 元素:所有 HTML 元素都由 HTMLElement 型別表示,具有下列標準特性

      • id,元素在文件中的唯一識別符號
      • title,有關元素的附加說明資訊,一般通過工具提示條顯示出來
      • lang,元素內容的語言程式碼,很少使用
      • dir,語言的方向,值為"ltr"(left-to-right,從左至右)或"rtl"(right-to-left,從右至左)
      • className,與元素的class 特性對應,即為元素指定的CSS類
      <div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div> 
      <script>
          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"; 
      </script>
    • 取得特性:getAttribute()方法

      • 傳遞給 getAttribute()的特性名與實際的特性名相同

        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()方法也可以取得自定義特性

        //<div id="myDiv" my_special_attribute="hello!"></div> 
        var value = div.getAttribute("my_special_attribute");
        //根據 HTML5 規範,自定義特性應該加上 data-字首以便驗證
      • 只有公認的(非自定義的)特性才會以屬性的形式新增到 DOM物件中

        //<div id="myDiv" align="left" my_special_attribute="hello!"></div> 
        alert(div.id); //"myDiv"
        alert(div.my_special_attribute); //undefined(IE 除外)
        alert(div.align); //"left" 
      • style屬性,用於通過 CSS 為元素指定樣式。在通過 getAttribute()訪問時,返回的 style 特性值中包含的是 CSS 文字,而通過屬性來訪問它則會返回一個物件

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

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

    • 設定特性:setAttribute()方法

      • 接受兩個引數:要設定的特性名和值。如果特性已經存在,setAttribute()會以指定的值替換現有的值;如果特性不存在,setAttribute()則建立該屬性並設定相應的值

        div.setAttribute("id", "someOtherId");
        div.setAttribute("class", "ft");
        div.setAttribute("title", "Some other text");
        div.setAttribute("lang","fr");
        div.setAttribute("dir", "rtl"); 
      • 通過 setAttribute()方法既可以操作 HTML 特性也可以操作自定義特性

      • 因為所有特性都是屬性,所以直接給屬性賦值可以設定特性的值

        //公認的(非自定義的)特性
        div.id = "someOtherId";
        div.align = "left"; 
        //自定義的屬性,該屬性不會自動成為元素的特性
        div.mycolor = "red";
        alert(div.getAttribute("mycolor")); //null(IE 除外)
    • 移除屬性:是 removeAttribute()方法

      • 用於徹底刪除元素的特性,不僅會清除特性的值,而且也會從元素中完全刪除特性

        div.removeAttribute("class"); 
      • 這個方法並不常用,但在序列化 DOM 元素時,可以通過它來確切地指定要包含哪些特性

    • attributes 屬性:attributes 屬性中包含一個NamedNodeMap,與 NodeList 類似

    • getNamedItem(name)方法:返回 nodeName 屬性等於 name 的節點。attributes 屬性中包含一系列節點,每個節點的 nodeName 就是特性的名稱,而節點的 nodeValue就是特性的值

      var id = element.attributes.getNamedItem("id").nodeValue; 
      var id = element.attributes["id"].nodeValue; //簡寫
      element.attributes["id"].nodeValue = "someOtherId"; //設定特性的值
    • removeNamedItem(name)方法:從列表中移除 nodeName 屬性等於 name 的節點。直接刪除具有給定名稱的特性,返回表示被刪除特性的 Attr 節點

      var oldAttr = element.attributes.removeNamedItem("id"); 
    • setNamedItem(node)方法:向列表中新增節點,以節點的 nodeName 屬性為索引

      element.attributes.setNamedItem(newAttr); 
    • item(pos):返回位於數字 pos 位置處的節點

    • 使用attributes 屬性遍歷元素的特性,在需要將 DOM 結構序列化為 XML 或 HTML 字串時,多數都會涉及遍歷元素特性

      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(" ");
      } 
    • 建立元素

      • 使用 document.createElement()方法可以建立新元素,只接受一個引數,即要建立元素的標籤名

        var div = document.createElement("div"); //建立
        div.id = "myNewDiv";                    
        div.className = "box"; 
        document.body.appendChild(div); //新增到文件
      • IE 中可以以另一種方式使用 createElement(),即為這個方法傳入完整的元素標籤,也可以包含屬性

      var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >"); 
      • 存在的問題:

        • 不能設定動態建立的<iframe>元素的 name 特性
        • 不能通過表單的 reset()方法重設動態建立的<input>元素
        • 動態建立的 type 特性值為"reset"的<buttou>元素重設不了表單
        • 動態建立的一批 name 相同的單選按鈕彼此毫無關係。name 值相同的一組單選按鈕本來應該用
          於表示同一選項的不同值,但動態建立的一批這種單選按鈕之間卻沒有這種關係
        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\">");
        } 
    • 元素的子節點

      • 元素可以有任意數目的子節點和後代節點,因為元素可以是其他元素的子節點

      • 元素的childNodes 屬性中包含了它的所有子節點,這些子節點有可能是元素、文字節點、註釋或處理指令

      • 元素也支援getElementsByTagName()方法。在通過元素呼叫這個方法時,除了搜尋起點是當前元素之外,其他方面都跟通過 document 呼叫這個方法相同,因此結果只會返回當前元素的後代

        var ul = document.getElementById("myList");
        var items = ul.getElementsByTagName("li"); 
  • Text型別

    • 概念:文字節點由 Text 型別表示,包含的是可以照字面解釋的純文字內容。純文字中可以包含轉義後的HTML 字元,但不能包含 HTML 程式碼

    • 特徵:

      • nodeType 的值為 3
      • nodeName 的值為"#text"
      • nodeValue 的值為節點所包含的文字
      • parentNode 是一個 Element
      • 不支援(沒有)子節點
    • 操作:可以通過 nodeValue 屬性或 data 屬性訪問 Text 節點中包含的文字,這兩個屬性中包含的值相同;對 nodeValue 的修改也會通過 data 反映出來,反之亦然。使用下列方法可以操作節點中的文字:

      • appendData(text):將 text 新增到節點的末尾

      • deleteData(offset, count):從 offset 指定的位置開始刪除 count 個字元

      • insertData(offset, text):在 offset 指定的位置插入 text

      • replaceData(offset, count, text):用text替換從offset指定的位置開始到offset+count為止處的文字

      • splitText(offset):從 offset 指定的位置將當前文字節點分成兩個文字節點

      • substringData(offset, count):提取從 offset 指定的位置開始到 offset+count 為止處的字串

      • length 屬性,儲存著節點中字元的數目,nodeValue.length 和 data.length 中也儲存著同樣的值

      • 在預設情況下,每個可以包含內容的元素最多隻能有一個文字節點,而且必須確實有內容存在

        <!-- 沒有內容,也就沒有文字節點 -->
        <div></div>
        <!-- 有空格,因而有一個文字節點 -->
        <div> </div>
        <!-- 有內容,因而有一個文字節點 -->
        <div>Hello World!</div> 
        <script>
          var textNode = div.firstChild; //或者 div.childNodes[0] 
            div.firstChild.nodeValue = "Some other message";
            //輸出結果是"Some &lt;strong&gt;other&lt;/strong&gt; message"
          div.firstChild.nodeValue = "Some <strong>other</strong> message";
        </script>
    • 建立文字節點

    • 使用document.createTextNode()建立新文字節點,這個方法接受一個引數:要插入節點中的文字;在建立新文字節點的同時,也會為其設定 ownerDocument 屬性

      //
      var element = document.createElement("div");
      element.className = "message";
      var textNode = document.createTextNode("Hello world!");
      element.appendChild(textNode);
      document.body.appendChild(element); 
      
      //一般情況下,每個元素只有一個文字子節點,在某些情況下也可能包含多個文字子節點
      //如果兩個文字節點是相鄰的同胞節點,那麼這兩個節點中的文字就會連起來顯示,中間不會有空格
      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); 
    • 一般情況下,每個元素只有一個文字子節點,在某些情況下也可能包含多個文字子節點,如果兩個文字節點是相鄰的同胞節點,那麼這兩個節點中的文字就會連起來顯示,中間不會有空格

      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); 
    • 規範化文字節點

      • 在一個包含兩個或多個文字節點的父元素上呼叫 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 操作的結果出現

    • 分割文字節點

    • 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 
  • Comment型別

    • 特徵:註釋在 DOM 中是通過 Comment 型別來表示的

      • nodeType 的值為 8
      • nodeName 的值為"#comment"
      • nodeValue 的值是註釋的內容
      • parentNode 可能是 Document 或 Element
      • 不支援(沒有)子節點
    • Comment 型別與 Text 型別繼承自相同的基類,因此它擁有除 splitText()之外的所有字串操作方法;與 Text 型別相似,也可以通過 nodeValue 或 data 屬性來取得註釋的內容

      <div id="myDiv"><!--A comment --></div>
      <script>
          //註釋節點可以通過其父節點來訪問
          var div = document.getElementById("myDiv");
          var comment = div.firstChild;
          alert(comment.data); //"A comment" 
          //使用 document.createComment()併為其傳遞註釋文字也可以建立註釋節點
          var comment = document.createComment("A comment "); 
      </script>
      
  • CDATASection型別

    • 概念:CDATASection 型別只針對基於 XML 的文件,表示的是 CDATA 區域。與 Comment 類似,
      CDATASection 型別繼承自 Text 型別,因此擁有除 splitText()之外的所有字串操作方法

    • 特徵:

      • nodeType 的值為 4;
      • nodeName 的值為"#cdata-section";
      • nodeValue 的值是 CDATA 區域中的內容;
      • parentNode 可能是 Document 或 Element;
      • 不支援(沒有)子節點
    • CDATA 區域只會出現在 XML 文件中,因此多數瀏覽器都會把 CDATA 區域錯誤地解析為 Comment
      或 Element

      <div id="myDiv"><![CDATA[This is some content.]]></div> 
    • 在真正的 XML 文件中,可以使用 document.createCDataSection()來建立 CDATA 區域,只需為其傳入節點的內容即可

  • DocumentType型別

    • 特徵:包含著與文件的 doctype 有關的所有資訊

      • nodeType 的值為 10;
      • nodeName 的值為 doctype 的名稱;
      • nodeValue 的值為 null;
      • parentNode 是 Document;
      • 不支援(沒有)子節點
    • 在 DOM1 級中,DocumentType 物件不能動態建立,而只能通過解析文件程式碼的方式來建立。持它的瀏覽器會把 DocumentType 物件儲存在 document.doctype 中 。 DOM1 級描述了DocumentType 物件的 3 個屬性:

      • name 表示文件型別的名稱
      • entities 是由文件型別描述的實體的 NamedNodeMap 物件
      • notations 是由文件型別描述的符號的NamedNodeMap 物件
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
      "http://www.w3.org/TR/html4/strict.dtd"> 
      <script>
          alert(document.doctype.name); //"HTML"
      </script>
  • DocumentFragment型別

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

    • 特徵:

      • nodeType 的值為 11;
      • nodeName 的值為"#document-fragment";
      • nodeValue 的值為 null;
      • parentNode 的值為 null;
      • 子節點可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或
        EntityReference
    • 使用:不能把文件片段直接新增到文件中,但可以將它作為一個“倉庫”來使用,即可以在裡面儲存將來可能會新增到文件中的節點。要建立文件片段,可以使用document.createDocumentFragment()方法

      var fragment = document.createDocumentFragment(); 
    • 文件片段繼承了 Node 的所有方法,通常用於執行那些針對文件的 DOM 操作

      <ul id="myList"></ul> 
      <script>
          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); 
      </script>
  • Attr型別

    • 概念:元素的特性在 DOM 中以 Attr 型別來表示。在所有瀏覽器中(包括 IE8),都可以訪問 Attr 型別
      的建構函式和原型。從技術角度講,特性就是存在於元素的 attributes 屬性中的節點

    • 特徵:

      • nodeType 的值為 2;
      • nodeName 的值是特性的名稱;
      • nodeValue 的值是特性的值;
      • parentNode 的值為 null;
      • 在 HTML 中不支援(沒有)子節點;
      • 在 XML 中子節點可以是 Text 或 EntityReference
    • 方法:儘管它們也是節點,但特性卻不被認為是 DOM 文件樹的一部分。開發人員最常使用的是 getAttribute()、setAttribute()和 remveAttribute()方法,很少直接引用特性節點

    • 屬性:Attr 物件有 3 個屬性:name、value 和 specified。其中,name 是特性名稱(與 nodeName 的
      值相同),value 是特性的值(與 nodeValue 的值相同),而 specified 是一個布林值,用以區別特性是在程式碼中指定的,還是預設的

    • 使用 document.createAttribute()並傳入特性的名稱可以建立新的特性節點

      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" 

2、DOM操作技術

  • 動態指令碼

    • 動態載入的外部 JavaScript 檔案能夠立即執行

      //<script type="text/javascript" src="client.js"></script> 
      function loadScript(url){
       var script = document.createElement("script");
       script.type = "text/javascript";
       script.src = url;
       document.body.appendChild(script);
      } 
      loadScript("client.js"); 
    • 指定 JavaScript 程式碼的方式是行內方式

      //<script type="text/javascript">function sayHi(){alert("hi");}</script>
      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');}");
  • 動態樣式

    • 使用<link>元素用於包含來自外部的檔案

      //<link rel="stylesheet" type="text/css" href="styles.css">
      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("styles.css");
    • 使用<style>元素用於包含嵌入式 CSS

      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 能夠在將來被修復

  • 操作表格

    • 傳統方法:使用 DOM 來建立下面的 HTML 表格

      //建立 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); 
    • HTML DOM 還為<table>添加了一些屬性和方法

      • caption:儲存著對元素(如果有)的指標
      • tBodies:是一個元素的 HTMLCollection
      • tFoot:儲存著對元素(如果有)的指標
      • tHead:儲存著對元素(如果有)的指標
      • rows:是一個表格中所有行的 HTMLCollection
      • createTHead():建立元素,將其放到表格中,返回引用
      • createTFoot():建立元素,將其放到表格中,返回引用
      • createCaption():建立元素,將其放到表格中,返回引用
      • deleteTHead():刪除元素
      • deleteTFoot():刪除元素
      • deleteCaption():刪除元素
      • deleteRow(pos):刪除指定位置的行
      • insertRow(pos):向 rows 集合中的指定位置插入一行
    • HTML DOM 還為<tbody>添加了一些屬性和方法

      • rows:儲存著元素中行的 HTMLCollection
      • deleteRow(pos):刪除指定位置的行
      • insertRow(pos):向 rows 集合中的指定位置插入一行,返回對新插入行的引用
    • HTML DOM 還為<tr>添加了一些屬性和方法

      • cells:儲存著元素中單元格的 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"));
      //將表格新增到文件主體中
      document.body.appendChild(table); 
  • 使用NodeList

  • 所有 NodeList 物件都是在訪問 DOM 文件時實時執行的查詢

    var divs = document.getElementsByTagName("div"),
     i,
     div;
    for (i=0; i < divs.length; i++){
        div = document.createElement("div");
        document.body.appendChild(div);
    } 
  • 如果想要迭代一個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);
    } 
  • 一般來說,應該儘量減少訪問 NodeList 的次數。因為每次訪問 NodeList,都會執行一次基於文件的查詢。所以,可以考慮將從 NodeList 中取得的值快取起來

3、總結

  • 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 操作