《JavaScript高級程序設計》(第3版)讀書筆記 第10章 DOM

  • DOM(文檔對象模型)是針對HTML和XML文檔的一個API(應用程序編程接口)。DOM描繪了一個層次化的節點樹,容許開發人員添加、移除和修改頁面的某一部分。DOM脫胎於Netscape及微軟公司創始的DHTML(動態HTML),但如今它已經成爲表現和操做頁面標記的真正跨平臺、語言中立方式。
  • 1998年10月DOM 1 級規範成爲W3C的推薦標準,爲基本的文檔結構及查詢提供了接口。本章主要討論與瀏覽器中的HTML頁面相關的DOM1級的特性和應用,以及JavaScript對DOM1級的視線。
  • IE中的全部DOM對象都是以COM對象的形式實現的。這意味着IE中的DOM對象與原生JavaScript對象的行爲或活動特色並不一致。本章將較多的談及這些差別。

節點層次

<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>
  • 能夠將這個簡單的HTML文檔視爲一個層次結構,如圖10-1所示。

![alt](img/charptor10_1.jpg)

  • 文檔節點是每一個文檔的根節點。在這個例子中,文檔節點只有一個子節點,既<html>元素,咱們稱之爲文檔元素
  • 文檔元素是文檔的最外層元素,文檔中的其餘全部元素都包含在文檔元素中。每一個文檔只能有一個文檔元素。在HTML頁面中,文檔元素始終都是<html>元素。在XML中,沒有預約義的元素,所以任何元素均可能成爲文檔元素。

Node 類型

  • DOM1級定義了一個 Node 接口,該接口將由 DOM 中全部節點類型實現。這個Node接口在JavaScript中是做爲Node類型實現的;除了IE以外,在其餘全部瀏覽器中均可以訪問到這個類型。
  • JavaScript中的全部節點類型都繼承自Node類型,所以全部節點類型都共享着相同的基本屬性和方法。
  • 每一個節點都有一個nodeType屬性,用於代表節點的類型。及誒單類型由在Node類型中定義的下列12個數值常量來表示,任何節點類型必居其一(編號爲節點類型常量存儲的數值):javascript

    1. Node.ELEMENT_NODE
    2. Node.ATTRIBUTE_NODE
    3. Node.TEXT_NODE
    4. Node.CDATA_SECTION_NODE
    5. Node.ENTITY_REFERENCE_NODE
    6. Node.ENTITY_NODE
    7. Node.PROCESSING_INSTRUCTION_NODE
    8. Node.COMMENT_NODE
    9. Node.DOCUMENT_NODE
    10. Node.DOCUMENT_TYPE_NODE
    11. Node.DOCUMENT_FRAGMENT_NODE
    12. Node.NOTATION_NODE
// 經過比較上面的常量,很容易的肯定節點類型
// 在IE中無效
if (someNode.nodeType == Node.ELEMENT_NODE) {
  console.log("Node is an element");
}

// 因爲IE沒有公開 Node 類型的構造函數
// 最好仍是將 nodeType 屬性與數字比較
if (someNode.nodeType == 1) {
  console.log("Node is an element");
}
  • 並非全部節點類型都受到Web瀏覽器的支持。開發人員最經常使用的就是元素和文本節點。

nodeNamenodeValue 屬性

  • 瞭解節點的具體信息,可使用nodeNamenodeValue 兩個屬性。這兩個屬性的值徹底取決於節點類型。在使用這兩個值之前,最好用上述的代碼檢查節點的類型。
if (someNode.nodeType == 1) {
  value = someNode.nodeName;            // nodeName的值是元素的標籤名
}

節點關係

  • 每一個節點都有一個childNodes屬性,其中保存着一個NodeList對象。注意,能夠經過方括號語法來訪問NodeList的值,並且也有length屬性,但它並非Array的實例
  • NodeList對象的獨特之處在於,它其實是基於DOM結構動態執行查詢的結果,所以DOM結構的變化可以自動反映在NodeList對象中。咱們常說NodeList是有生命、有呼吸的對象,而不是咱們第一次訪它的瞬間拍攝下來的一張快照。
// 方括號和 item() 語法結果是相同的
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = comeNode.childNodes.length;

// 雖然不是Array的實例,但咱們能夠將它轉換成數組
// 在IE8及以前的版本中無效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
// 因爲IE8及更早版本將 NodeList 實現爲一個 COM 對象
// 必須手動枚舉全部成員,才能轉換成數組
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中的全部節點都具備相同的父節點,而相互之間是同胞節點。
  • 經過每一個節點的previousSiblingnextSibling 屬性能夠訪問同一列表中的其餘節點。列表第一個節點previousSiblingnull,列表最後一個nextSiblingnull,固然若是列表只有一個節點,那麼兩個都是null
  • 父節點的firstChildlastChild屬性分別指向第一個和最後一個。若是列表沒有節點,那麼兩個屬性都是null

圖片描述

  • hasChildNodes()也是一個很是有用的方法,當查詢節點存在子節點時返回true,不存在返回false。這是比查詢childNodes.length更簡單的方法。
  • 全部節點都有的最後一個屬性是ownerDocument,該屬性指向表示整個文檔的文檔節點。這種關係表示的是任何節點都屬於它所在的文檔,任何節點都不能同時存在兩個或更多個文檔中。經過這個屬性,咱們能夠沒必要在節點層次中經過層層回溯達到頂端,而是能夠直接訪問文檔節點。

操做節點

  • 由於關係指針都是隻讀的,因此DOM提供了一些操做節點的方法 。
  • 最經常使用的方法是appendChild(),用於向childNodes列表的末尾添加一個節點,執行後,方法返回新增的節點。
var returnedNode = someNode.appendChild(newNode);
console.log(returnedNode == newNode);           // true
console.log(someNode.lastChild ==newNode);      // true
  • 若是須要把節點放在childNodes列表中某個特定的位置上,而不是放在末尾,可使用insertBefore()方法。這個方法接收兩個參數:要插入的節點和做爲參照的節點。插入節點後,被插入的節點會變成參照節點的前一個同胞節點(previousSibling),同時被方法返回。若是參照節點是null,則 insertBefore()appendChild() 執行相同操做。
// 插入後成爲最後一個子節點
var returnedNode = someNode.insertBefore(newNode, null);

// 插入後成爲第一個子節點
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);

// 插入後在最後一個子節點前面
var returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
  • replaceChild() 替換節點。一樣接收兩個參數,插入的節點和參照節點。插入新的節點並將參照節點從文檔樹中移除,新的節點會從被替換的節點複製全部關係指針。儘管從技術上講,被替換的節點仍然在文檔中,但它在文檔中的位置已經不存在了。
  • removeChild() 移除節點。被移除的節點仍然在文檔中,但它在文檔中的位置已經不存在了。
  • 以上四個方法必須先取得操做節點的父節點(代碼示例中是someNode)。在不存在子節點的節點上調用以上方法,會致使錯誤。

其餘方法

  • 還有兩個方法是全部節點都有的。
  • cloneNode() 用於建立調用這個方法的節點的一個徹底相同的副本。接收一個布爾值參數,表示是否執行深複製。css

    • 傳入true。執行深複製,複製節點及其整個子節點樹
    • 傳入false。執行淺複製,即只複製節點自己。
    • 複製返回的節點副本屬於文檔全部,但並無爲它制定父節點。所以這個節點副本就成爲了一個「孤兒」,除非經過 appendChild() insertBefore() replaceChild() 將它添加到文檔中。
    • IE8及以前的版本不會爲包含空白符的文字建立節點(TEXT)
    • clone() 方法不會複製添加到DOM節點中的JavaScript屬性,例如時間處理程序。這個方法只複製特性、(在明確指定的狀況下也複製)子節點,其餘一切都不會複製。
    • IE 會複製事件處理程序,因此咱們建議在複製以前最好先移出事件處理程序。
<ul id="ul">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
</ul>
var myList = document.getElementById("ul");
var deepList = myList.cloneNode(true);
// [text, li, text, li, text, li, text]
console.log(deepList.childNodes);
// 3 (IE < 9) 或 7 (其餘瀏覽器)
// IE8及以前的版本不會爲包含空白符的文字建立節點(TEXT)
console.log(deepList.childNodes.length);

var shallowList = myList.cloneNode(false);
console.log(shallowList.childNodes.length);     // 0
  • normalize() 方法惟一的做用就是處理文檔樹中的文本節點。因爲解析器的實現或DOM操做等緣由,可能會出現文本節點不包含文本,或者鏈接出現兩個節點的狀況。當在某個節點上調用這個方法時,就會在該節點的後代節點中查找上述兩種狀況。html

    • 若是找到了空文本節點,則刪除它
    • 若是找到相鄰的文本節點,則將它們合併爲一個文本節點
    • 本章後面還將進一步討論方法
var html = document.documentElement;               // 取得對<html>的引用
console.log(html == document.childNodes[0]);       // true
console.log(html == document.firstchild)           // true
  • 全部瀏覽器都支持document.documentElementdocument.boyd 屬性
  • Document另外一個可能的子節點是DocumentType。一般將<!DOCTYPE>標籤當作一個與文檔其餘部分不一樣的實體,能夠經過doctype屬性(在瀏覽器中是document.doctype)來訪問信息。
  • 瀏覽器對document.doctype的支持差異很大,因此這個屬性的用途頗有限:java

    • IE8及以前版本,若是存在文檔類型聲明,會將其錯誤的解釋爲一個註釋並把它當作Comment節點;而document.doctype的值始終爲null
    • IE9+,若是存在文檔類型聲明,則將其做爲文檔的第一個子節點;document.doctype是一個DocumentType節點,也能夠經過document.firstChilddocument.childNodes[0]訪問同一個節點。
    • Safari, Chrome, Opera :若是存在文檔類型聲明,則將其解析,但不做爲文檔的子節點。document.doctype是一個DocumentType節點,但該節點不會出如今document.childNodes中。
  • 從技術上說,出如今<html>元素外部的註釋應該是算是文檔的子節點。然而,不一樣的瀏覽器在是否解析這些註釋以及可否正確處理他們等方面,也存在很大差別。
<!-- 第一條註釋 -->
<html>
  <body>
  </body>
</html>
<!-- 第二條註釋 -->
  • 看起來這個頁面應該有3個子節點:註釋、<html>元素、註釋。從邏輯上講,咱們會認爲document.childNodes中應該包含與這3個節點對應的3項。可是實際上,瀏覽器存在如下差別:node

    • IE8及以前版本、Safari3.1及更高版本、Opera和Chrome 只爲第一條註釋建立節點,不爲第二條註釋建立節點。結果第一條註釋就會成爲document.childNodes中的第一個子節點。
    • IE9+,將會將兩條都建立節點。
    • Firefox 和 Safari3.1以前的版本會徹底忽略這兩條註釋。
  • 多數狀況下,咱們都用不着在document對象上調用appendChild() removeChild() replaceChild() 方法,由於文檔類型(若是存在的話)是隻讀的,並且它只能有一個元素子節點(該節點一般早就已經存在了)。

文檔信息

  • 做爲HMLTDocument的一個實例,document對象還有一些標準的Document對象所沒有的屬性。
  • title 包含着<title>元素中的文本。經過這個屬性能夠取得當前頁面的標題,也能夠修改當前頁面的標題並反映在瀏覽器的標題欄中。修改title屬性的值會改變<title>元素。
// 取得文檔標題
var originalTitle = document.title;

// 設置文檔標題
document.title = "New page title";
  • 下面三個屬性與網頁的請求有關,全部這些信息都存在於請求的HTTP頭部,只不過是經過這些屬性讓咱們可以在JavaScript中訪問它們而已:編程

    • URL屬性中包含頁面完整的URL(地址欄中的URL)
    • domain屬性中值包含頁面的域名
    • referrer屬性中可能會包含空字符串
    • URLdomain屬性是相互關聯的。例如document.URL等於"http://www.wrox.com/WileyCDA/",那麼document.domain就等於"www.wrox.com"。
    • 3個屬性中只有domain能夠設置,但有安全方面的限制。若是URL中包含一個子域名,例如"p2p.wrox.com",那麼就只能講domain設置爲"wrox.com"(URL中包含"www",如"www.wrox.com"時,也是如此)。
    • 當頁面中包含來自其餘子域的框架或內嵌框架時,可以設置document.domain就很是方便了。因爲跨域安全限制,來自不一樣子域的頁面沒法經過JavaScript通訊。而經過將每一個頁面的document.domain設置爲相同的值,這些頁面就能夠互相訪問對方包含的JavaScript對象了。
// 取得完整的URL
var url = document.URL;

// 取得域名
var domain = document.domain;

// 取得來源 頁面的URL
var referrer = document.referrer;
  • 瀏覽器對domain屬性還有一個限制,即若是域名一開始是「鬆散的」(loose),那麼就不能將它再設置爲「緊繃的」(tight)。
// 假設頁面來自於 p2p.wrox.com域

document.domain = "wrox.com";         // 鬆散的(成功)
document.domain = "p2p.wrox.com";     // 緊繃的(出錯)

查找元素

  • getElementById() 接收一個參數:要取得的元素的ID。找到相應的元素則返回該元素,不然返回null跨域

    • IE8及較低版本不區分ID大小寫
    • 若是頁面多個元素的ID相同,只會返回第一個匹配的元素。
    • IE7及更早的版本添加了一個怪癖:name特性與給定ID匹配的表單元素也會被該方法返回。
    <input type="text" name="myElement" value="Text field">
    <div id="myElement">A div</div>
    <script>
      // IE7中調用會返回<input>元素
      var el = document.getElementById("myElement");
    </script>
  • getElementsByTagName() 接收一個參數:要取得的元素的標籤名,而返回的是包含零或多個元素的NodeList。可使用方括號語法或item()方法來訪問對象中的項。
  • namedItem() 使用這個方法能夠經過元素的name特性取得集合中的項。或方括號語法能達到一樣的效果
<img src="myimage.gif" name="myImage">

<script>
  var images = document.getElementsByTagName("img");

  console.log(images.length);
  console.log(images[0].src);                           // 方括號傳入數值就調用 item()
  console.log(images.item(0).scr);
  var myImage = images.namedItem("myImage");
  var myImage = images["myImage"];                      // 方括號傳入字符串就調用namedItem()
</script>
  • 要取得文檔中的全部元素,能夠向getElementsByTagName()中傳入"*"。在JavaScript及CSS中,星號一般表示所有。
  • 雖然標準規定標籤名須要區分大小寫,但爲了最大限度的與既有HTML頁面兼容,傳給getElementsByTagName()的標籤名是不須要區分大小寫的。但對於XML頁面而言(包括XHTML),getElementsByTagName()方法就會區分大小寫。
  • getElementByName() 是隻有HTMLDocument類型纔有的方法,返回帶有給定name屬性的全部元素。最常使用的狀況是取得單選按鈕;爲了確保發送給瀏覽器的值正確無誤,全部單選按鈕必須具備相同的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>
  • 上述例子使用getElementsByName()方法能夠返回三個input元素。可是對於這裏的單選按鈕來講namedItem()方法只會取得第一項(由於每一項的name特性都相同)。

特殊集合

  • document.anchors 包含文檔中全部帶name特性的<a>元素
  • document.applets 包含文檔中全部的<form>元素,與document.getElementsByTagName("form")獲得的結果相同
  • document.images 包含文檔中全部的<img>元素,與document.getElementsByTagName("img")獲得的結果相同
  • document.links 包含文檔中全部帶 href 特性的<a>元素

DOM一致性檢測

  • 因爲DOM分爲多個級別,也包含多個部分,所以檢測瀏覽器實現了DOM的哪些部分就十分必要。document.implementation屬性就是爲此提供的,與瀏覽器對DOM的實現直接對應。
  • DOM1級別只爲document.implementation規定了一個方法,即hasFeature()。接收兩個參數:要檢測的DOM功能的名稱及版本號。若是支持返回true
var hasXmlDom = docuemnt.implementation.hasFeature("XML", "1.0");
  • 下表列出了能夠檢測的不一樣值得版本號
功能 版本號 說明
Core 1.0、2.0、3.0 基本的DOM,用於描述表現文檔的節點樹
XML 1.0、2.0、3.0 Core的XML拓展,添加了對CDATA、處理指令及實體的支持
HTML 1.0、2.0 XML的HTML拓展,添加了對HTML特有元素及實體的支持
Views 2.0 基於某些樣式完成文檔的格式化
StyleSheets 2.0 將樣式表關聯到文檔
CSS 2.0 對層疊樣式表1級的支持
CSS2 2.0 對層疊樣式表2級的支持
Events 2.0, 3.0 常規的DOM事件
UIEvents 2.0, 3.0 用戶界面事件
MouseEvents 2.0, 3.0 由鼠標引起的事件(click、mouseover等)
MutationEvents 2.0, 3.0 DOM樹變化時引起的事件
HTMLEvents 2.0 HTML4.01事件
Range 2.0 用於操做DOM樹種某個範圍的對象和方法
Traversal 2.0 遍歷DOM樹的方法
LS 3.0 文件與DOM樹之間的同步加載和保存
LS-Asnyc 3.0 文件與DOM樹之間的異步加載和保存
Validation 3.0 在確保有效的前提下修改DOM樹的方法
  • hasFeature() 方法確實方便,但也有缺點。由於實現者能夠自行決定是否與DOM規範的不一樣部分保持一致。事實上,想讓hasFearture()針對全部值都有返回true很容易,但返回true有時候也不意味着實現與規範一致。
  • 爲此咱們建議,在使用hasFreatrue()以外,還同時使用能力檢測。

文檔寫入

  • write()writeln()方法都接收一個字符串參數,即要寫入到輸出流中的文本。wirte()會原樣寫入,而writeln()則會在字符串的末尾添加一個換行符(n)。在頁面加載的過程當中,可使用這兩個方法動態的加入內容。
  • 在包含JavaScript文件時,必須注意不能像下面的例子那樣直接包含字符串"</script>",由於這會致使該字符串被解釋爲腳本塊的結束,後面的代碼將沒法執行。使用轉義"</script>"能夠避免這個問題。
  • open()close()分別用於打開和關閉網頁的輸出流。若是是在頁面加載期間使用write()writeln()方法,則不須要用到這兩個方法。
  • 嚴格型XHTML文檔不支持文檔吸入。對於那些按照application/xml+xhtml內容類型提供的頁面,這兩個方法也一樣無效。

Element類型

  • Element類型用於表現XML或XHTML元素,提供了對元素標籤名、子節點及特性的訪問。
  • Element類型具備如下特徵:數組

    • nodeType的值爲1
    • nodeName的值爲元素的標籤名
    • nodeValue的值爲null
    • parentNode的值可能爲Dcoment或Element
    • 其子節點多是 ElementTextCommentProcessingInstructionCDATASectionEntityReference
  • 訪問元素的標籤名,可使用nodeName屬性,也能夠是使用tagName屬性,這兩個屬性會返回相同的值。
var div = document.getElementById("myDiv");
console.log(div.tagName);                   // "DIV"
console.log(div.nodeName);                  // "DIV"
console.log(div.tagName == div.nodeName);   // true
if (element.tagName == "div") {
  // 不能這樣比較,很容易出錯
}

if (element.tagName.toLowerCase() == "div") {
  // 推薦這樣作(適用於任何文檔)
}

HTML元素

  • 全部HTML元素都由HTMLElement類型表示。HTMLElement類型直接繼承自Elment並添加了一些屬性。每一個HTML元素中都存在的下列標準特性:瀏覽器

    • id 元素在文檔中的惟一標識符
    • title 有關元素的附加說明信息,通常經過工具提示條顯示出來
    • lang 元素內容的語言代碼,不多使用
    • dir 語言的方向值爲"ltr"(left-to-right 從左至右)或 "rtl"
    • className 與元素的class特性對應,即爲元素指定的CSS類。沒有將這個屬性命名爲class是由於class是ECMAScript的保留字。
  • 並非對全部屬性的修改都會在頁面中直觀的表現出來。對id或lang的修改對用戶而言是透明不可見的。而對title的修改則只會在鼠標移動到這個元素之上時纔會顯示出來。對dir的修改會在屬性重寫的那一刻,當即影響頁面中文本的左右對齊方式。修改className時,若是新類關聯了與此前不一樣的CSS樣式,就當即應用新的樣式。
  • 下面表格列出了全部HTML元素以及與之關聯的類型(以斜體印刷的元素表示不推薦使用了)。注意表中的這些類型在Opera、Safari、Chrome、Firefox中均可以經過JavaScript訪問,但在IE8以前的版本中,不能經過JavaScript訪問。

圖片描述
圖片描述

取得特性

  • 操做特性的DOM方法主要有三個,分別是getAttribute()setAttribute()removeAttribute()
var div = document.getElemntByid("myDiv");
console.log(div.getAttribute("id"));          // "myDiv"
console.log(div.getAttribute("class"));          // "bd"
console.log(div.getAttribute("title"));          // "Body Text"
console.log(div.getAttribute("lang"));          // "en"
console.log(div.getAttribute("dir"));          // "ltr"
  • 注意,傳遞給getAttribute()的特性名與實際的特性名相同。所以想要獲得class特性值,應該傳入"class" 而不是"className",後者只在經過對象屬性訪問特性時才用。
  • 若是給定的特性不存在,getAttribute()返回null
  • 也能夠取得自定義特性,即標準HTML語言中沒有的特性的值。須要注意,特性的名稱不區分大小寫,即"ID" 和 "id" 表明的都是同一個特性。另外也要注意,根據HTML5規範,自定義特性應該加上data-前綴以便驗證。
  • 任何元素的全部特性,也均可以經過DOM元素自己的屬性來訪問。固然HTMLElement也會有5個屬性與相應的特性一一對應。不過只有公認的(非自定義)特性纔會以屬性的形式添加到DOM對象中。例如能夠經過div.id訪問div元素的id屬性。不過自定義特性在Safari、Opera、Chrome、Firefox中是不存在的,但IE卻會爲自定義特性也建立屬性。
  • CSS經過getAttribute()訪問時,返回的style特性值中包含的是CSS文本,而經過屬性來訪問它則會返回一個對象。因爲style屬性是用於以編程方式訪問元素樣式的(本章後面討論),所以並無直接映射到style特性。
  • 時間處理程序(例如onclick)經過getAttribute()訪問,返回的是相應的代碼字符串。而在訪問onclick屬性時,則返回的是一個JavaScript函數(若是未在元素中指定相應特性,則返回null)。這是由於onclick及其餘事件程序屬性自己就應該被賦予函數值。
  • 因爲存在上述差異,在經過JavaScript以編程方式操做DOM時,開發人員不常用 getAttribute()方法,而只是使用對象的屬性。只有在取得自定義特性值得狀況下,纔會使用getAttribute()方法。
  • 在IE7及之前版本中,經過getAttribute()訪問style特性或onclick,返回的值與屬性相同,都返回對象值或函數值。雖然IE8已經修復了這個bug,但不一樣IE版本間的不一致性,也是致使開發人員不適用getAttribute()訪問HTML特性的一個緣由。

設置特性

  • getAttribute()對應的方法時setAttribute()這個方法接收兩個參數:要設置的特性名和值。若是特性已經存在,setAttribute()會以指定的值替換現有的值;若是特性不存在,則建立該屬性並設置相應的值。
  • setAttribute()方法既能夠操做HTML特性也能夠操做自定義特性。經過這個方法設置的特性名會統一轉換爲小寫形式,即"ID"最終變成"id"。
div.setAttribute("id", "someOtherId");
div.id = "someOtherId";

// 添加自定義屬性,該屬性不會自動成爲元素的特性
div.mycolor = "red";
div.getAttribute("mycolor");   // null ie除外
  • removeAttribute() 用於完全刪除元素的特性,調用這個方法不只會清楚特性的值,並且也會從元素中徹底刪除特性。這個方法並不經常使用,IE6及之前版本不支持。
div.removeAttribute("class");

attributes屬性

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

    • getNamedItem(name):返回nodeName屬性等於name的節點
    • removeNamedItem(name):從列表中移除nodeName屬性 等於name的節點
    • setNameItem(node):向列表中添加節點,以節點的nodeName屬性爲索引
    • item(pos):返回位於數字pos位置處的節點
  • attributes屬性中包含一系列節點,每一個節點的nodeName就是特性的名稱,而節點的nodeValue就是特性的值。
// 取得元素的id
var id = element.attributes.getNamedItem("id").nodeValue;

// 設置元素的id
element.attributes["id"].nodeValue = "someOtherId";

// 刪除元素id,並返回被刪除特性的Attr節點
var oldAttr = element.attributes.removeNamedItem("id");

// 傳入一個新的特性節點
element.attributes.setNameItem(newAttr);
  • 因爲attributes的方法不夠方便,所以開啊人員更多的會使用getAttribute()removeAttribute()setAttribute()方法。若是想要遍歷元素特性,能夠用attributes
  • 針對attributes對象中的特性,不一樣瀏覽器返回的順序不一樣。
  • IE7及更早版本返回HTML元素中全部可能的特性,包括沒有指定的特性。返回100多個特性是常見的
// 迭代元素的每個特性,而後構形成 name="value"字符串
function outputAttributes(element) {
  var pairs = new Array(),
      attrName,
      attrValue,
      i,
      len;
  for (i=0, len=elment.attributes.length; i < len; i++) {
    attrName = element.attributes[i].nodeName;
    attrValue = element.attributes[i].nodeValue;
    // 針對 IE7- 作兼容
    // 根據specified屬性,只返回指定的特性
    if (element.attributes[i].specified) {
      paris.push(attrName + "=\"" + attrValue + "\"");
    }
  }
  return pairs.join(" ");
}

建立元素

  • document.createElement()方法能夠建立新元素。只接收一個參數,即要建立元素的標籤名,在HTML文檔中不區分大小寫,而在XML(包括XHTML)文檔中,則是區分大小寫。
  • document.createElement()建立元素的同時,也爲新元素設置了ownerDcoument屬性。此時還能夠操做元素的特性,爲它添加更多子節點。
  • 因爲新元素還沒有被添加到文檔樹中,所以設置這些特性不會影響瀏覽器的顯示。要把新元素添加到文檔樹,可使用appendChild() insertBefore() replaceChild()方法。
// 建立
var div = document.createElement("div");
// 操做元素特性,添加子節點
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
  • 在IE中能夠傳入完整的元素標籤,也能夠包含屬性(僅IE支持)。這樣有助於避開在IE7及更早版本中動態建立元素的某些問題:

    • 不能設置動態建立的<iframe>元素的name特性
    • 不能經過表單的reset()方法重設動態建立的<input>元素(第13章討論reset()方法)
    • 動態建立的type特性值爲「reset」的<button>元素重設不了表單
    • 動態建立的一批name相同的單選按鈕彼此毫無關係。
if (client.browser.id && client.browser.ie <= 7) {
  var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div>");
}

元素的子節點

  • 元素能夠有任意書目的子節點和後臺節點,由於元素能夠是其餘元素的子節點。元素的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>
  • 若是須要經過childNodes屬性遍歷子節點,那麼必定不要忘記瀏覽器間的這一差異。這意味着在執行某項操做之前,一般都要先檢查nodeType屬性
for (var i=0, len = element.childNodes.length; i < len; i++) {
  if (element.childNodes[i].nodeTpe == 1) {
    ...
  }
}
  • 若是想經過某個特性的標籤名取得子節點或後代節點,能夠經過元素調用getElementsByTagName()方法,結果只會返回當前元素的後代。
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");

Text類型

  • 文本節點由Text類型表示,包含的是能夠照字面量解釋的純文本內容。純文本中能夠包含轉義後的HTML字符,但不能包含HTML代碼。
  • Text節點具備如下特徵:

    • nodeType的值爲3
    • nodeName的值爲'#text'
    • nodeValue的值爲節點所包含的文本
    • parentNode是一個Element
    • 不支持(沒有)子節點
  • 能夠經過nodeValue屬性或data屬性訪問Text節點中包含的文本,這兩個屬性的值相同。對nodeValue的修改也會經過data反映出來,反之亦然。
  • 使用下列方法能夠操做節點中的文本

    • appendData(text):將text添加到節點的末尾
    • deleteData(offset, count):從offset指定的位置插入text
    • insertData(offset, text):在offset指定的位置插入text
    • replaceData(offset, count, text):用text替換從offset指定的位置開始到 offset+count爲止處的文本
    • splitText(offset):從offset指定的位置將當前文本節點分紅兩個文本節點。
    • substringData(offset, count):提取從offset指定的位置開始到 offset+count爲止處的字符串
    • length屬性:保存着節點中字符的書目。並且nodeValue.lengthdata.length中也保存着一樣的數值
  • 在默認狀況下,每一個能夠包含內容的元素最多隻能有一個文本節點,並且必須確實有內容存在
<!-- 沒有內容,也就沒有文本節點 -->
<div></div>

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

<!-- 有內容,由於有一個文本節點 -->
<div>Hello World!</div>
// 能夠像這樣取得文本子節點
var textNode= div.firstChild;   // 或者 div.childNodes[0]

// 取得文本節點的引用後,就能夠修改它了
div.firstChild.nodeValue = "Some other message";
  • 若是這個文本節點當前存在於文檔樹中,那麼修改文本節點的結果就會當即獲得反映。
  • 修改文本節點時,字符串會通過HTML(或XML,取決於文檔類型)編碼。換言之,小於號、大於號或引號都會像下面的例子同樣被轉義
div.firstChild.nodeValue = "Some <strong>other</strong> message";
// 輸出結果:"Some &lt;strong&gt;other&lt;/strong&gt; message"
  • 這是在向DOM文檔中插入文本以前,先對其進行HTML編碼的一種有效方式

建立文本節點

  • document.createTextNode()建立新的文本節點。與設置已有文本節點的值同樣,做爲參數的文本也將按照HTML或XML的格式進行編碼。
var textNode = document.createTextNode("<strong>Hello</strong> World!");
  • 在建立新文本節點的同時,也會爲其設置ownerDocument屬性。不過除非把新節點添加到文檔樹中已經存在的節點中,不然咱們不會在瀏覽器窗口中看到新節點。
var element = document.createElement("div");
elment.className = "message";

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

document.body.appendChild(element);
  • 通常狀況下,每一個元素只有一個文本子節點。不過在某些狀況下也可能包含多個文字子節點。相鄰的同胞文本節點,之間會連起來,中間不會有空格。
var element = document.createElement("div");
elment.className = "message";

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

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

document.body.appendChild(element);

規範化文本節點

  • DOM文檔中存在相鄰的同胞文本節點很容易致使混亂,由於分不清文本節點之間的界限。因而催生了一個可以將相鄰文本節點合併的方法。
  • normalize()方法是由Node類型定義的(於是在全部節點類型中都存在)。若是在一個包含多個文本節點的父元素上調用normalize()方法,則會將全部文本節點合併成一個文本節點。
var element = document.createElement("div");
elment.className = "message";

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

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

document.body.appendChild(element);

console.log(element.childNodes.length);   // 2

element.normalize();
console.log(element.childNodes.length);   // 1
console.log(element.firstChild.nodeValue); // "Hello World!Yippee!"
  • 瀏覽器在解析文檔時永遠不會建立相鄰的文本節點,這種狀況只會做爲DOM操做的結果出現。
  • normalize()有時候會致使IE6崩潰,IE7以上修復了此問題。

分割文本節點

  • splitText()方法會將一個文本節點分割成兩個。
var element = document.createElement("div");
elment.className = "message";

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

document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);
console.log(element.firstChild.nodeValue);                   // "Hello"
console.log(newNode.nodeValue);                              // " World!"
console.log(element.childNodes.length);                      // 2

Comment類型

  • 註釋在DOM中是經過Comment類型來表示的。Comment節點具備如下特徵:

    • nodeType的值爲8
    • nodeName的值爲 "#comment"
    • nodeValue的值是註釋的內容
    • parentNode多是Dcoment或Element
    • 不支持(沒有)子節點
  • Comment類型與Text類型繼承自相同的基類,所以它擁有除splitText()以外的全部字符串操做方法。
<div id="myDiv"><!--A comment--></div>
var div = document.getElementById("myDiv");
var comment = div.firstChild;
console.log(comment.data);                     // "A comment"
  • 使用document.createComment()併爲其傳遞註釋文本也能夠建立註釋節點
var comment = document.createComment("A comment ");
  • 開發人員不多會建立和訪問註釋節點,此外瀏覽器也不會識別位於</html>標籤後的註釋。若是要訪問註釋節點,必定要保證它們是位於<html></html>之間。

CDATASection類型

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

    • nodeType的值爲4
    • nodeName的值爲"#cdata-section"
    • nodeValue的值是CDATA區域中的內容
    • parentNode多是DocumentElement
    • 不支持(沒有)子節點
  • CDATA區域只會出如今XML文檔中,所以多數瀏覽器都會把CDATA區域錯誤的解析爲Comment或Element。
<div id="myDiv"><![CDATA[This is some content.]]></div>
  • 這個例子中div元素應該包含一個CDATASection節點。但四大主流瀏覽器都不能正確解析。即便對於有效的XHTML頁面,瀏覽器也沒有正確的支持嵌入的CDATA區域。
  • 在真正的XML文檔中,可使用document.createCDataSection()來建立CDATA區域。

DocumentType類型

  • DocumentType類型在Web瀏覽器中並不經常使用,僅有 Firefox Safari 和 Opera支持它。

    • nodeType的值爲10
    • nodeName的值爲doctype的名稱
    • nodeValue的值是null
    • parentNodeDocument
    • 不支持(沒有)子節點
  • 一般,瀏覽器中的文檔使用的都是HTML或XHTML文檔類型,只有name屬性是有用的。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
console.log(document.doctype.name);          // "HTML"
  • IE不支持DocumentType,所以 document.doctype的值始終都是null

DocumentFragment 類型

  • 全部節點類型中,只有DocumentFragment在文檔中沒有對應的標記。
  • DOM規定文檔片斷(document fragment)是一種輕量級的文檔,能夠包含和控制節點,但不會像完整的文檔那樣佔用額外的資源。

    • nodeType的值爲11
    • nodeName的值爲"#document-fragment"
    • nodeValue的值是null
    • parentNodenull
    • 子節點能夠是ElementProcessingInstructionCommentTextCDATASectionEntityReference
  • 雖然不能把文檔文段直接添加到文檔中,但能夠將它做爲一個倉庫來使用,在裏面保存未來可能會添加到文檔中的節點。
  • document.createDocumentFragment() 方法建立文檔片斷
<ul id="myList"></ul>
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;

// 若是直接向ul添加li元素會致使瀏覽器反覆渲染
// fragment做爲一個元素中轉的倉庫避免了這個問題
for (var i=0; i < 3; i++) {
  li = document.createElement("li");
  li.appendChild(document.createTextNode("Item " + (i+1)));
  fragment.appendChild(li);
}

// 這裏只會將fragment的全部子節點添加到ul上
// 而fragment自己永遠不會成爲文檔樹的一部分
ul.appendChild(fragment);

Attr類型

  • 元素的特性在DOM中以Attr類型來表示。在全部瀏覽器中(包括IE8),均可以訪問 Attr類型的構造函數和原型。

    • nodeType的值爲2
    • nodeName的值就是特性的名稱
    • nodeValue的值就是特性的值
    • parentNodenull
    • HTML中不支持(沒有)子節點
    • XML中子節點能夠是Text或EntityReference
  • 儘管Attr是節點,但特性卻不被認爲是DOM文檔樹的一部分。
  • Attr對象有三個屬性:name value specified
  • document.createAttribute()傳入特性的名稱能夠建立新的特性節點。
var attr = document.createAttribute("align");
attr.value = "left";
element.setAttribute(attr);
console.log(element.attributes["align"].value);      // left
console.log(element.getAttributeNode("align").value);      // left
console.log(element.getAttribute("align"));           // left

DOM操做技術

動態腳本

  • 使用<script>元素能夠向頁面中插入JavaScript代碼,一種是經過其src特性包含外部文件,另外一種就是用這個元素自己包含代碼。
  • 動態加載的JavaScript文件可以當即運行。
// 在執行最後一行代碼把<script>元素添加到頁面中以前
// 是不會下載外部文件的
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "client.js";
document.body.appendChild(script);
<script type="text/javascript" src="client.js"></script>
  • 遺憾的是,並無什麼標準方式來探知腳本是否加載完成。
  • 從邏輯上講,使用行內方式直接插入代碼是有效的。在Firefox Safari Chrome Opera中,均可以正常運行,但在IE中,會致使錯誤。IE將<script>視爲一個特殊元素,不容許DOM訪問其子節點。不過可使用<script>元素的text屬性來制定JavaScript代碼
var script = document.createElement("script");
script.type = "text/javascript";

// 這樣IE不支持
script.appendChild(
  document.createTextNode("function sayHi() { console.log('Hi')}")
);
// 可使用`<script>`元素的text屬性來制定JavaScript代碼
script.text = "function sayHi() { console.log('Hi')}";

document.body.appendChild(script);
  • Safari3以前的版本不支持這種寫法,能夠這樣作兼容處理
var script = document.createElement("script");
script.type = "text/javascript";
var code = "function sayHi() { console.log('Hi')}"

try {
  script.appendChild(document.createTextNode(code));
} catch (ex) {
  script.text = code;
}

document.body.appendChild(script);
  • 實際上,這樣執行代碼與在全局做用域中把相同的字符串傳遞給eval()是同樣的。

動態樣式

  • 與動態腳本相似,所謂動態樣式是指在頁面剛加載時不存在的樣式;動態樣式是在頁面加載完成後動態添加到頁面中的。
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(head);
<link rel="stylesheet" type="text/css" href="styles.css">
  • 必須將<link>元素添加到<head>而不是<body>元素,才能保證所在瀏覽器中的行爲一致。
  • 加載外部樣式文件的過程是異步的,也就是加載樣式 與執行JavaScript代碼的過程沒有固定的次序。
  • 通常是否知道樣式已加載完成並不重要,但也存在幾種利用事件來檢測這個過程是否完成的技術,將在第13章討論。
  • 行內方式插入樣式也是能夠的,一樣要對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;
  }
  document.getElementsByTagName("head")[0].appendChild(style);
}
  • 若是專門針對IE編寫代碼,務必當心使用styleSheet.cssText屬性。在重用同一個<style>元素並再次設置這個屬性時,有可能致使瀏覽器崩潰。一樣將cssText屬性設置爲空字符串也可能致使瀏覽器崩潰。

操做表格

  • <table>元素是HTML中最複雜的結構之一。想要建立表格,通常都必須涉及表示表格行、單元格、表頭等方面。因爲涉及的標籤多,於是使用核心DOM方法建立和修改表格每每都免不了要編寫大量的代碼。
<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代碼很長,還有點很差理解。爲了方便構建表格,HTMLDOM還爲<table> <tbody> <tr> 元素添加了一些屬性和方法。
  • <table>元素添加的屬性和方法:

    • caption: 保存着對<caption>元素(若是有)的指針
    • tBodies: 是一個<tbody>元素的HTMLCollction
    • 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(0);
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 及其近親 NamedNodeMapHTMLCollection,是從總體上透徹理解DOM的關鍵所在。這三個集合都是動態的,每當文檔結構發生變化,它們都會獲得更新。
  • 本質上說,全部NodeList對象都是在訪問DOM文檔實時運行的查詢。
// 下列代碼會致使無限循環
var divs = document.getElementsByTagName("div");
var div;

// 每次循環都要對條件 i < divs.length 求值
// 但每次循環都添加了一個新的div
for (var i=0; i < divs.length; i++) {
  div = document.createElement("div");
  document.body.appendChild(div);
}
// 最好使用length屬性初始化第二個變量
var divs = document.getElementsByTagName("div");
var i, len, div;

// len保存着第一次循環時div的數量,不會隨着循環增長
for (i=0, len=divs.length; i < len; i++) {
  div = document.createElement("div");
  document.body.appendChild(div);
}
  • 儘可能減小訪問NodeList的次數,由於每次訪問都會運行一次基於文檔的查詢。能夠考慮將從NodeList中取得的值緩存起來。
  • 理解DOM的關鍵就是理解DOM對性能的影響。DOM操做每每是JavaScript程序中開銷最大的部分。有鑑於此,最好減小DOM操做。
相關文章
相關標籤/搜索