DOM

本章內(nèi)容:理解包含不同層次節(jié)點的DOM、使用不同的節(jié)點類型、克服瀏覽器兼容性問題及各種陷阱;本篇文章主要基于DOM1級進行講解,DOM2和DOM3后續(xù)會提到。

DOM(文檔對象模型)時針對于HTML和XML文檔的一個API(應用程序接口),DOM描繪了一個層次化的節(jié)點樹,允許開發(fā)人員添加、移除、修改頁面的某一部分。DOM脫始于Netscaoe及微軟公司創(chuàng)建的DHTML(動態(tài)HTML)

一、節(jié)點層次

DOM可以將任何HTML或XML文檔描繪成一個由多層節(jié)點構(gòu)成的結(jié)構(gòu)。每個節(jié)點都擁有各自的特點,數(shù)據(jù)和方法,另外也與其他節(jié)點存在某種關系。節(jié)點之間的關系構(gòu)成了層次,而所有頁面標記則表現(xiàn)為一個以特定節(jié)點未根節(jié)點的樹形結(jié)構(gòu)。

<html>
<head>
  <title>Sample page</title>
</head>
<body>
  <p>Hello World</p>
</body>
</html>

每一段標記都可以通過數(shù)中的一個節(jié)點來表示;HTML元素通過元素節(jié)點表示,特性(attribute)通過特性節(jié)點表示,文檔類型通過文檔類型節(jié)點表示,注釋可以通過注釋節(jié)點表示。總共又十二種節(jié)點類型,這些節(jié)點類型都繼承自一個基類型。

1.1、Node 類型

DOM1級定義了一個Node接口,由DOM中的所有節(jié)點類型實現(xiàn)。除了IE低版本(IE8及以下)外,在其他瀏覽器中都可以訪問這個類型。所有節(jié)點類型都繼承自Node類型,所有節(jié)點類型都共享著相同的基本屬性和方法。

節(jié)點類型由在Node類型中定義的下列12給數(shù)值常量來表示,任何節(jié)點類型必居其一:

  • Node.ELEMENT_NODE(1)
  • Node.ATTRIBUTE_NODE(2)
  • Node.TEXT_NODE(3)
  • Node.CDATA_SECTION_NODE(4)
  • Node.ENTTY_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)

可以通過這些常量,可以很容易地確定節(jié)點的類型

var someNode = document.getElementsByTagName('div')[0]
if (someNode.nodeType == Node.ELEMENT_NODE) { // IE8及以下 中無效
  // todo
}

由于低版本的IE沒有公共Node類型的構(gòu)造函數(shù),為了確??鐬g覽器兼容,最好還是將nodeType屬性與數(shù)字值進行比較。

if (someNode.nodeType == 1) {
  // todo
}
1.1.1、 nodeName 和 nodeValue 屬性

了解節(jié)點的 具體信息可以使用,nodeName 和 nodeValue 這兩個屬性。

if (someNode.nodeType == 1) {
  console.log(someNode.nodeName)
}

對于元素節(jié)點來說,nodeName 中保存的始終都是元素的標簽名,而nodeValue的值始終為null

1.1.2、 節(jié)點關系

文檔中所有的節(jié)點之間都存在這樣那樣的關系。每個節(jié)點都有一個childNodes屬性,其中保存著一個NodeList對象。是一種類似數(shù)值對象,保存一組有序的節(jié)點,可以通過位置來訪問這些節(jié)點;雖然可以通過方括號語法來訪問,而且這個對象也有l(wèi)ength屬性,但它并不是Array的實例。NodeList對象的獨特之處在于、他實際上是基于DOM結(jié)構(gòu)動態(tài)執(zhí)行查詢的結(jié)構(gòu),因此DOM結(jié)構(gòu)的變化能夠自動反映在NodeList對象中。

訪問NodeList中的節(jié)點可以通過方括號或者 item() 方法

var someNode = document.getElementsByTagName('div') // HTMLCollection
var childs = someNode[0].childNodes // NodeList
console.log(childs[0]) // 第一個節(jié)點
console.log(childs.item(1)) // 第二個節(jié)點
console.log(childs.length) // 長度

方括號語法頗受開發(fā)人員的青睞,因為看起來更像訪問數(shù)組。

前面介紹過,對 arguments 使用 Array.prototype.slice() 方法可以將其轉(zhuǎn)換為數(shù)組。而采用同樣的方法,也可以將NodeList對象轉(zhuǎn)換為數(shù)組

console.log(Object.prototype.toString.call(childs)) //[object NodeList]
var arrayOfNodes = Array.prototype.slice.call(childs, 0)

console.log(Object.prototype.toString.call(arrayOfNodes)) //[object Array]

在IE8及更早版本中將 NodeList 實現(xiàn)為一個 COM對象,要想在IE中將NodeList轉(zhuǎn)換為數(shù)組,必須手動枚舉所有成員。

function convertToArray(nodes) {
  var array = null
  try {
   array = Array.prototype.slice.call(childs, 0)
  }  catch(e) {
    array = new Array()
    for(var i = 0, len = nodes.length; i < len; i++) {
      array.push(nodes[i])
    }
  }
  return array
}

節(jié)點的 parentNode 屬性,該屬性指向文檔樹種的父節(jié)點。previousSibling 指向 當前節(jié)點前面的兄弟節(jié)點。nextSibling 指向 當前節(jié)點的后面的兄弟節(jié)點。列表中第一個previousSiblings的值為null,同樣列表中最好一個節(jié)點的 nextSiblings也為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')
}

父節(jié)點與其第一個和最后一個子節(jié)點之間也存在特殊關系。父節(jié)點的 firstChildlastChild 分別指向其 childNodes中 第一個和最后一個子節(jié)點。其中 firstChild = childNodes[0];lastChild = childNodes[childNodes.length - 1]明確這些關系能夠?qū)ξ覀儾檎液驮L問文檔結(jié)構(gòu)中的節(jié)點提高極大的便利。如圖 10-2

圖 10-2

hasChildNodes()
此外,hasChildNodes() 也是一個非常有用的方法,這個方法在節(jié)點包含一或多個子節(jié)點的情況下返回

if (someNode.hasChildNodes()) {
  // todo
}

ownerDocument
所有節(jié)點都有的最后一個屬性是 ownerDocument,該屬性指向表示整個文檔的文檔節(jié)點。通過這個屬性,我們可以不必在節(jié)點層次中通過層層回溯到達頂端,而是可以直接訪問文檔節(jié)點。

1.1.3、 操作節(jié)點

因為關系指針式只讀的,DOM提供了一些操作節(jié)點的方法。最常用的方法式 appendChild(),用于向 childNodes 列表的末尾添加一個節(jié)點。更新完成后,appendChild() 返回新增的節(jié)點。

appendChild()

var returnedNode = someNode.appendChild(newNode)
console.log(returnedNode == newNode)
console.log(returnedNode == someNode.lastChild)

如果傳入到appendChild()中的節(jié)點已經(jīng)是文檔的一部分了,那結(jié)果就是將該節(jié)點從原來的位置轉(zhuǎn)移到新位置。

// someNode由多個子節(jié)點
var returnedNode = someNode.appendChild(someNode.firestChild)
console.log(returnedNode == someNode.lastChild) // true
console.log(returnedNode == someNode.firstChild)

insertBefore()
如果需要把節(jié)點放在 childNodes 列表中某個特定的位置上,可以使用 insertBefore() 方法。接受兩個參樹:插入的節(jié)點、參照的節(jié)點。插入的節(jié)點會變成參照節(jié)點的前一個同胞節(jié)點,同時被返回。如果參照節(jié)點是 null,則insertBefore() 與 appendChild() 執(zhí)行相同的操作。

// 參照物為null,插入后稱為最后一給子節(jié)點
returnedNode = someNode.insertBefore(newNode, null)
console.log(newNode == someNode.lastChild) // true

// 插入后成為第一個子節(jié)點。
returnedNode = someNode.insertBefore(newNode, someNode.firstNode)
console.log(newNode == someNode.firstChild) // true
console.log(returnedNode == newNode) // true

// 插入到最后一個子節(jié)點前面
returnedNode = someNode.insertBefore(newNode, someNode.lastNode)
console.log(newNode == someNode.lastNode.previousSibling) // true

replaceChild()
replaceChild()方法接受兩個參數(shù):要插入的節(jié)點、要替換的節(jié)點。替換的節(jié)點將由這個方法返回并從文檔書中被移除,同時由要插入的節(jié)點占據(jù)其位置。

// 移除第一個子節(jié)點
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild)

// 替換最后一個子節(jié)點
returnedNode = someNode.replaceChild(newNode, someNode.lastChild)

removeChild()
如果指向移除而非替換節(jié)點,可以使用 removeChild() 方法。接受一個參數(shù),即要移除的節(jié)點。被移除的節(jié)點將成為方法的返回值。

// 移除第一個節(jié)點
var formerFirstChild = someNode.removeChild(someNode.firstNode)

// 移除最后一個節(jié)點
formerLastChild = someNode.removeChild(someNode.lastNode)

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

1.1.4、 其他方法

有兩個是所有類型的節(jié)點都有的,他們發(fā)別是 cloneNode() 和 normalize()

cloneNode()
姐搜一個布爾值,表示是否執(zhí)行深復制。深復制:復制節(jié)點及整個子節(jié)點樹;淺復制:即只復制節(jié)點本身。復制后返回的節(jié)點副本屬于文檔所有,但并沒有為它指定父節(jié)點。因此,這個節(jié)點副本就成為了一個“孤兒”,而非通過appendChild()、insertBefore()、replaceChild()將它添加到文檔中。

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

如果我們已經(jīng)將<ul>元素的引用保存在了變量 myList 中

var deepList = myList.cloneNode(true)
console.log(deepList.childNodes.length) // 3 (ie < 9) 或 7 (其他瀏覽器)

deepList = myList.cloneNode(false)
console.log(deepList.childNodes.length) // 0

normalize()
這個方法的作用是主力文檔樹的文本節(jié)點。由于解析器的實現(xiàn)或DOM操作等原因,可能會出現(xiàn)文本節(jié)點不包含文本,或者接連出現(xiàn)兩個文本節(jié)點的情況,調(diào)用改方法后,當前節(jié)點的后代節(jié)點如果存在上述的任意一種問題。如果找到空白文本節(jié)點,則刪除它;如果找到相鄰文本節(jié)點,則合并它們

var element = document.createElement('div') // 創(chuàng)建一個根元素
element.className = 'message'

var textNode = document.createTextNode('Hello') // 創(chuàng)建一個文本節(jié)點
element.appendChild(textNode)

var anotherTextNode = document.createTextNode('World') // 創(chuàng)建第二個文本節(jié)點
element.appendChild(anotherTextNode)

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

element.normalize() // 規(guī)范化文本節(jié)點
console.log(element.childNodes.length) // 1

1.2、Document類型

Javascript 通過 Document 類型表示文檔。document 對象是 HTMLDocument(繼承自 Document 類型)的一個實例,表示整個HTML頁面。document對象是window對象的一個屬性,因此可以將其作為全局對象來訪問。Document具有以下特征:

  • nodeType 的值為 9
  • nodeName 的值為“#document”
  • nodeValue 的值為null
  • parentNode 的值為 null
  • ownerDocument 的值為 null
  • 其子節(jié)點可能是一個 DocumentType(最多一個)、Element(最多一個)、ProcessingInstruction 或 Comment
1.2.1、 文檔的子節(jié)點

Document節(jié)點的子節(jié)點由兩個內(nèi)置的訪問快捷方式。分別是:documentElement 指向HTML頁面中的<html>元素;childNodes 列表訪問文檔元素,但通過documentElement 屬性則能更快捷、更直接地訪問該元素。

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

如上文檔結(jié)構(gòu)

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

此外還有一個body 屬性,直接指向 <body> 元素

var body = document.body // 獲取<body>的引用

所有的瀏覽器都支持 document.documentElement 和 document.body屬性


Document 另外一個可能的子節(jié)點是 DocumentType。通常將<!DOCTYOE>標簽看成一個與文檔其他部分不同的實體,可以通過doctype屬性(document.doctype)來訪問它的信息。

console.log(document.doctype) // <!doctype html>

瀏覽器對document.doctype的支持差別很大,可給出如下總結(jié):

  • IE8 及之前版本:如果存在文檔類型聲明,將會被錯誤地解釋為一個注釋并把它當作 Comment(注釋)節(jié)點;而document.doctype的值始終未null
  • IE9+ 及 Firefox:如果存在文檔類型聲明,則將其作為文檔的第一個子節(jié)點;document.doctype 是一個 DocumentType 節(jié)點,也可以通過 document.firstChild 或 document.childNodes[0]來訪問同一個節(jié)點
  • Safari、Chrome 和 Opera:如果存在文檔類型聲明,則將其解析,但不作為文檔的子節(jié)點。document.doctype 是一個 DocumentType 節(jié)點,但該節(jié)點不會出現(xiàn)在 document.childNodes中。
<!-- 第一條注釋 -->
<html>
  <body>

  </body>
</html>
<!-- 第二條注釋 -->

這個頁面該有3個子節(jié)點:注釋、<html>元素、注釋?,F(xiàn)實中瀏覽器在處理位于html 外部的注釋方面存在如下差異:

  • IE8 及以前版本、Safari3.1及更高版本、Opera 和 Chrome 只為第一條注釋創(chuàng)建節(jié)點,部位第二條注釋創(chuàng)建節(jié)點。結(jié)果,第一條注釋就會成為 document.childNodes 中的第一個子節(jié)點。
  • IE9 及更高版本會將第一條注釋創(chuàng)建未 document.childNodes 中的一個注釋節(jié)點,也會將第二條注釋創(chuàng)建為 document.childNodes 中的注釋子節(jié)點。
  • Firefox 以及 Safari3.1 之前的版本會完全忽略這兩條注釋。

同樣,瀏覽器的這種不一致性導致了位于<html>元素外部的注釋沒有什么用處。

1.2.2、 文檔信息

作為HTMLDocument的一個實例document對象還有一些標準的Document對象所沒有的屬性。其中一個屬性是 title,包含著<title>元素中的文本。

// 取得文檔標題
console.log(document.title)

// 設置文檔標題
document.title = 'Happy New Day'

接下來介紹的三個屬性都和網(wǎng)頁的請求有關:URL、domain、referrer,這些信息都存在與HTTP頭部。只不過通過這些屬性我們可以在JavaScript中進行訪問

URL
包含頁面完整的URL(及地址欄中顯示的URL)

// 取得完整的 URL
var url = document.URL

domain
domain屬性中只包含頁面的域名

// 取得頁面的域名
var domain = document.domain

referrer
referrer屬性中保存著連接到當前頁面的那個頁面的URL。在沒有來源頁面的情況下,referrer屬性中可能會包含空字符串。

// 獲取來源頁面的URL
var referrer = document.referrer

這三個屬性中只有 domain 是可以設置的。但由于安全方面的限制,也并非可以給domain設置任何值。不能將這個屬性設置未URL 中不包含的域。

// 假設頁面來自 p2p.wrox.com

document.domain = "wrox.com" // 成功
document.domain = "nczonline.net" // 失敗

當頁面包含來自其他子域的框架或內(nèi)嵌框架時,能夠設置 document.domain 就非常方便了。由于跨域安全限制,來之不同子域的頁面無法通過 JavaScript 通信。通過將每個頁面的 document.domain 設置相同的值,這些頁面就可以互相訪問對象包含的JavaScript對象

domain還有一個限制,即如果域名一開始是"松散的"(loose),那么不能將它再設置為"緊繃的"(tight)。換句話說,在將 document.domain 設置為 "wrox.com"之后,就不能再將其設置會"p2p.wrox.com"

// 假設頁面來自于 p2p.wrox.com域
document.domain = "wrox.com" // 松散的(成功)
document.domain = "p2p.wrox.com" // 緊繃的(失敗)

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

1.2.3、 查找元素

說到最常用的DOM應用,恐怕就要數(shù)取得特定的某個或某組元素的引用,然后再執(zhí)行一些操作了。Document類型唯一提供了兩個方法:

getElementById()
接受一個參數(shù):要去的的元素的ID。如果不存在則返回null。這里的ID必須也頁面中元素的id特性(attribute)嚴格匹配,包括大小寫。如果多個ID相同,則返回 文檔中第一次出現(xiàn)的元素。

// 獲取一個 id=container 的DOM元素
const container = document.getElementById('container')

在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>
<!-- 在IE7中,使用 getElementById('myElement') 會返回 input 元素-->

getElementsByTagName()
接受一個參數(shù),既要取得元素的標簽名,返回的是包含零或多個元素的 NodeList。在HTML文檔中,這個方法會返回一個 HTMLCollection 對象,作為一個“動態(tài)”集合,該對象與NodeList非常類似。

var images = document.getElementsByTagName('img')

與NodeList 對象類似,則可以使用方括號或item()方法來訪問 HTMLCollection 對象中的項。長度可以通過 length 屬性來獲取

console.log(images.length)
console.log(images[0] == images.item(0)) // true

HTMLCollection 對象還有一個方法,叫做 namedItem(),使用這個方法可以通過元素的 name 特性取得集合中的項。

images.nameItem('importantImg') // 或去images 集合中,name屬性未 importantImg的 元素

也可以使用方括號來表示按 name 值 訪問

images['importantImg']

如果項獲取文檔中的所有元素,可以通過 getElementsByTagName(“*”) 來獲取。

// 獲取所有元素
var allElements = document.getElementsByTagName("*")

由于IE(8及以下)將注釋(Comment)實現(xiàn)為元素 (ELement),因此在IE中調(diào)用 會返回所有的注釋節(jié)點。

getElementsByName()
這個方法是 HTMLDocument才有的。返回符合給定 name 值的所有元素;常用于獲取單選按鈕

// 獲取 所有name 為 habit 的單選按鈕
var allRadio = document.getElementsByName('habit')
1.2.4、 特殊集合

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

  • document.anchors:包含文檔中所有帶name 特性的 <a> 元素
  • document.forms:包含文檔中所有的<form>元素,與document.getElementsByTagName('form') 所得到的結(jié)果一致
  • document.images: 包含文檔所有的<img> 元素
  • document.links:包含文檔中所有帶 href 特性的 <a> 元素

這個特殊集合始終可以通過HTMLDocument 對象訪問到,而且,與HTMLCollection對象類似,集合中的項也會隨著當前文檔的內(nèi)容而跟新。

1.2.5、 DOM一致性檢測

由于DOM分為多個級別,document.implementation為此提供相應信息和功能的對象,與瀏覽器對DOM的實現(xiàn)直接對應。DOM1級只為document.implementaion 規(guī)定了一個方法
hasFeature(feature, version)
接受兩個參數(shù):要檢測的DOM功能、版本號。如果瀏覽器支持給定名稱和版本的功能,返回 true

var hasXmlDOm = document.impementation.hasFeature('XML', '1.0')

可供檢測的其他值及版本號

多數(shù)情況下,在使用DOM的某些特殊功能之前,最好除了檢測 hasFeature() 之外,還同時使用能力檢測。

1.2.6、 文檔寫入

輸出流寫入到網(wǎng)頁中,提供4個方法:
write() 、writeln()、open()、close()

write() 和 writeln() 方法都接受一個字符串參數(shù),即要寫入到輸出流中的文本。write() 會原樣寫入,writeln()則會在 末尾添加一個 換行符(\n)

<html>
  <head>
    <title>example</title>
  </head>
  <body>
  
    <script>
      document.write("<script type=\"text/javascript\" src=\"./client.js\"><\/script>")
    </script>
  </body>
</html>

如果在文檔加載結(jié)束后在調(diào)用document.write(),那么輸出的呢人將會重寫整個頁面。如下:

window.onload = function() {
  document.write('onload')
}

方法 open() 和 close() 分別用于打開和關閉網(wǎng)頁的輸出流。如果是在頁面加載期間使用 write() 或 writeln() 方法,則不需要用到這兩個方法。

1.3、Element 類型

Element類型是用于XML或HTML 元素,提供了對元素標簽名、子節(jié)點 及 特性的訪問。具有以下特征。

  • nodeType 的值為1
  • nodeNmae 的值為元素的標簽名
  • nodeValue 的值為null
  • parentNode 可能是 Document 或 Element
  • 其子節(jié)點可能是 Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference

要法官問元素的標簽名,可以使用 nodeName屬性,也可以使用 tagName屬性:會返回相同的值

// <div id="div"></div>
var element = document.getElementById('div') // 獲取一個 id 為 div 的元素
console.log(element.nodeName) // DIV
console.log(element.tagName == element.nodeName) // true

在HTML中標簽名始終都以全部大寫展示:而在XML(有時候也包括XHTML)中,標簽名始終會與源代碼中的保持一致。加入你不確定自己的腳本將會在HTML還是XML文檔中執(zhí)行,最好是在比較之前將標簽名轉(zhuǎn)換為相同的大小寫形式。

if (element.tagName.toLowerCase() === 'div') { (始運用任何文檔)
  // todo
}
1.3.1、 HTML 元素

所有HTML元素都由 HTMLElement 類型表示,通過它的子類型來表示。HTMLElement類型直接繼承自 Element 并添加了一些屬性。

  • id,元素在文檔中的唯一標識符
  • title,有關元素的附加說明信息,一般通過工具提示條顯示出來。
  • lang,元素內(nèi)容的語言代碼,很少使用。
  • 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>

上述的屬性 也可以通過 JavaScript 獲取 或 修改

var div = document.getElementByIId('id')

console.log(div.id) // myDiv
div.id = 'otherId'
1.3.2、 取得特性

操作特性的DOM方法主要有三個:getAttribute()、setAttribute()、removeAttribute()。這三個方法可以爭對任何特性使用,包括那些以 HTMLElement類型屬性的定義的特性。
getAttribute(attr)
接受一個參數(shù):要獲取的特性名。傳遞的特性名必須于實際的特性名相同。如果給定名稱的特性不存在,則返回 null。這方法也可以取得自定義特性。

<!-- 根據(jù) h5 規(guī)范 自定義屬性由 data-開頭 -->
<div id="div" name="dv" data-width="200">
var div = document.getElementById('div')

console.log(div.id) // div
console.log(div.dataWidth) // undefined (IE8及以下 除外,有效值)
console.log(div.getAttribute('data-width')) // 200

因為id在HTML中式公認特性,因此改元素的DOM對象中也將存在對應的屬性,自定義特性 data-width 在 Safari、Opera、Chrome、Firefox中是不存在的;但IE卻會為自定義屬性也創(chuàng)建屬性。


由兩類特殊的特性,雖然由對應的屬性名,但屬性的值與通過 getAttribute()返回的值并不相同。

第一類特性是 style,返回style特性值中包含的是 CSS 文本,而通過屬性來訪問則會返回一個對象。

    <div id="div" name="dv" data-width="200" style="width:200px;height:200px;background:cyan;">
var div = document.getElementById('div')

console.log(div.style) // 返回對象 CSSStyleDeclaration
console.log(div.getAttribute('style')) // 返回字符串 width:200px;height:200px;background:cyan;

第二類特性是onclick這類的事件處理程序,通過 getAttribute() 訪問會返回相應的代碼字符串。而在訪問 onclick 屬性是,則會返回一個 JavaScript 函數(shù)(如果未知的則返回 null)

<div id="div" onclick="alert('morning')">
var div = document.getElementById('div')

console.log(div.onclick) // 返回函數(shù) ? onclick(event) { alert('morning') }
console.log(div.getAttribute('onclick')) // 返回字符串  alert('morning')

由于這些差別的存在,經(jīng)常使用 屬性 來獲取,只有在取得自定義特性值的情況下,才會使用到getAttribute() 方法。

1.3.3、設置特性

與 getAttribute() 對應的方法是 setAttribute()

setAttribute(attr, value)
這個方法接受兩個參數(shù):設置的特性名、值。
如果設置的特性值已存在,則會被指定的值替換;如果不存在則會創(chuàng)建該屬性并設置相應的值。
通過setAttribute()方法既可以操作HTML特性也可以操作自定義特性。通過這個方法設置的特性名會被統(tǒng)一轉(zhuǎn)換為小寫形式,即”ID“最終會變成”id“

document.getElementById('div').setAttribute('data-width', '600')

removeAttribute(attr)
接受一個參數(shù):要刪除的特性。
這個方法用于徹底刪除元素的特性,不久會清除特性的值,也會從元素中完全刪除特性。

document.getElementById('div').removeAttribute('class')

這個方法并不常用,但在序列化DOM元素時,可以通過它來確定的指定要包含哪些特性。IE6及以前版本不支持。

1.3.4、 attributes 屬性

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

  • getNamedItem(name):返回nodeName屬性等于 name的節(jié)點
  • removeNamedItem(name):從列表中移除 nodeName 屬性等于 name 的節(jié)點
  • setNamedItem(Node):向列表中添加節(jié)點,以節(jié)點的nodeName屬性為索引。
  • item(pos):返回位于數(shù)字pos位置處的節(jié)點。

attributes 屬性中包含一系列節(jié)點,每個節(jié)點的nodeName 就是特性的名稱,而節(jié)點的nodeValue 就是特性的值。比如得元素的id特性。
getNameItem(name)

var id = element.attributes.getNamedItem('id').nodeValue
// 使用方括號的簡寫方式
id = element.attriutes['id'].nodeValue

也可以通過這種方式來進行設置值

element.attributes['id'].nodeValue = 'someOtherId'

removeNamedItem(name)
這個方法與在元素上調(diào)用 removeAttribute() 方法的效果相同。唯一的區(qū)別是 removeNamedItem()返回表示被刪除特性的Attr節(jié)點

var oldAttr = element.attributes.removeNamedItem('id') // id="xxx"

setNamedItem(node)
這個方法是一個不常用的方法,通過這個方法可以為元素添加一個新特性。

element.attributes.setNamedItem(newAttr)

一般來說,前面介紹的 attributes 的方法不夠方便,因此開發(fā)人員更多的會使用 getAttribute()、setAttribute()、removeAttribute()方法

`不過在遍歷元素的特性的時候,attributes屬性倒是可以派上用處。

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(" ")
}

關于以上代碼的運行結(jié)果,有以下兩點必要的說明。

  • 針對 attributes 對象中的特性,不同瀏覽器的順序不同
  • IE7 及更早的版本會返回HTML 元素中所有的可能性,包括沒有指定的特性。

可以對以上函數(shù)進行改進,讓它只返回被指定的特性,每個特性節(jié)點都有一個名為 specified的屬性,如果這個值為 true,意味著 在 HTML中指定了相應的 特性,要么是通過 setAttribute() 方法設置了該特性。在IE中,所有未設置國的特性的該屬性都為false,而在其它瀏覽器中根本不會為這類特性生成對應的特性節(jié)點(因此,在這些瀏覽器中,任何特性節(jié)點的 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.attribues[i].specified) pairs.push(attrName + '="' + attrValue + '"')
  }
  return pairs.join(" ")
}

經(jīng)過 specified 屬性的判斷,這個函數(shù)只返回指定的特性

1.3.5、 創(chuàng)建元素

document.createElement(tagName)
通過這個方法可以創(chuàng)建新元素:接受一個參數(shù),即要創(chuàng)建元素的標簽名。標簽名在HTML文檔中不區(qū)分大小寫,但是在XML文檔中區(qū)分大小寫。

var div = document.createElement('div')

在創(chuàng)建新元素的同時,也會為新元素設置了 ownerDocument屬性(指向整個文檔節(jié)點 #docment)。此時還可以操作元素的特性,為他添加子節(jié)點等。

div.id = 'myNewDiv'
div.className = 'box'

要把新創(chuàng)建的元素添加到文檔樹,可以使用 appendChild()、insertBefore()、replaceChild()方法

document.body.appendChild(div)

在IE(8及以下)中可以以另一種方式使用 createElement(),即為這個方法傳入完整的元素標簽,也可以包含屬性:

var div = document.createElement('<div id="myDiv" class="box"></div>')

這種方式有助于避開在IE7及更早版本中動態(tài)創(chuàng)建元素的某些問題。如下:

  • 不能設置動態(tài)創(chuàng)建的<iframe>元素的name特性
  • 不能通過表單的reset()方法重設動態(tài)創(chuàng)建的<input>元素
  • 動態(tài)創(chuàng)建的type特性值為”reset“的 <button>元素重設不了表單
  • 動態(tài)創(chuàng)建的一批name相同的單選按鈕彼此毫無關系。name值相同的一組單選按鈕本來應該用于表示同一選項的不同值,但動態(tài)創(chuàng)建的一批這種單選按鈕之間卻沒有這種關系。
    上述的這些問題,皆可以通過在 createElement() 中指定完整的HTML標簽來解決
if (client.browser.ie && client.browser.ie <= 7) { // 代理檢測
  // 創(chuàng)建一個 帶name 特性的 iframe 元素
  var iframe = document.createElement('<iframe name="myframe"></iframe>')

  // 創(chuàng)建 input 元素
  var input = document.createElement('<input type="checkbox">')
  
  // 創(chuàng)建button 元素
  var button = document.createElement('<button type="reset"></button>')
  
  // 創(chuàng)建單選按鈕
  var radio1 = document.createElement('<input type="radio" name="choice">')
  var radio2 = document.createElement('<input type="radio" name="choice">')
}

與使用 createElement() 的慣常方式一樣,會返回一個DOM元素的引用??梢詫⑦@個元素添加到文檔中,也可以對齊增強。

1.3.6、 元素的子節(jié)點

元素可以由任意數(shù)目的子節(jié)點和后代節(jié)點,這些子節(jié)點有可能是元素、文本節(jié)點、注釋、處理指令。不同瀏覽器在看待這些節(jié)點方面存在顯著的不同。如下:

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

如果是 IE來解析這些代碼,那么<ul>將會有3個子節(jié)點,分別是3個<li>元素。
如果是其他瀏覽器,則會有7給元素,包括3個<li>元素和4個文本節(jié)點(每個<li>之間的空白符)。如果像下面這樣將元素間的空白符刪除,那么所有瀏覽器都會返回相同數(shù)目的子節(jié)點。

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

如果需要通過childNodes 屬性遍歷子節(jié)點,那么一定不要忘記瀏覽器間的這一差別。通常都要先檢查 nodeType 屬性。如下:

for (var i = 0, len = element.childNodes.length; i < len; i++){
  if (element.childNodes[i].nodeType == 1) { // 元素節(jié)點 Elmondayement類型
    // todo
  }
}

1.4、Text類型

文本節(jié)點由Text類型表示,包含的是可以照字面解釋的村文本內(nèi)容。純文本可以包換轉(zhuǎn)義后的HTML字符,但不能包含HTML代碼。Text節(jié)點具有以下特征:

  • nodeType 的值為 3;
  • nodeName 的值為”#text“
  • nodeValue 的值為節(jié)點所包含的文本
  • parentNode 是一個Element
  • appendData(text):將 text 添加到節(jié)點的末尾
  • deleteData(offset, count): 從offset指定位置開始刪除 count給字符
  • insertData(offset, text):substringDa在offset指定的位置插入 text
  • replaceData(offset, count, text):用text 替換從 offset 指定的位置開始到 offset+count 為止處的文本
  • splitText(offset):從offset指定的位置將當前文本節(jié)點分成兩個文本節(jié)點。
  • substringData(offset, count):提取從 offset 指定的位置開始到 offset + count 為止處的字符串。

除了上述的這些,文本節(jié)點還有一個length 屬性。

在默認情況下,每個可以包含內(nèi)容的元素最多只能有一個文本解節(jié)點,而且必須確實有內(nèi)容存在。

<!-- 沒有內(nèi)容,也就沒有文本節(jié)點 -->
<div></div>

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

<!-- 有內(nèi)容,因而有一個文本節(jié)點 -->
<div>Hello World</div>

在取得文本節(jié)點的引用后,可以像下面這樣來修改它

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

// 修改節(jié)點
textNode.nodeValue = 'Some other message'

如果這個文本節(jié)點當前存在于文本樹中,那么修改文本節(jié)點的結(jié)果就會立即得到反映。該文本節(jié)點時,字符串會經(jīng)過HTML(或XML,取決于文檔類型)編碼。換句話說,大于號、小于號或引號會被轉(zhuǎn)義。

1.4.1、 創(chuàng)建文本節(jié)點

document.createTextNode(text)
可以使用 document.createTextNode()創(chuàng)建新文本節(jié)點,這個方法接受一個參數(shù)——要插入節(jié)點中的文本。

var textNode = document.createTextNode('<strong>Hello</strong> World') 
1.4.2、 規(guī)范文本節(jié)點

normalize()
前面提到過這個方法,DOM文檔中存在相鄰的同胞文本節(jié)點很容易導致混亂,于是催生了一個能夠即將相鄰文本節(jié)點合并的方法。在一個包含兩個或多個文本節(jié)點的父元素上調(diào)用 normalize() 方法,則會將所有文本節(jié)點合并成一個節(jié)點。

瀏覽器在解析文檔時永遠不會創(chuàng)建相鄰的文本節(jié)點,這種情況只會作為操作DOM操作的結(jié)果出現(xiàn)。

1.4.3、 分割文本節(jié)點

splitText(pos)
Text類型提供了一個作用于 normalize() 相反的方法: splitText()。將一個文本節(jié)點分成兩個文本節(jié)點,即按照指定的位置分割 nodeValue 值。該方法返回指定分隔符之后剩余的文本節(jié)點。

var div = document.getElementById('div')
var textNode = div.firstChild // Hello World
console.log(div.childNodes.length) // 1

var newNode = textNode.splitText(5) 
console.log(div.childNodes.length) // 2

console.log(div.firstChild) // Hello
console.log(newNode) // World

1.5、 Comment 類型

注釋在DOM中式通過 Comment 類型來表示的。Comment節(jié)點具有以下特征。

  • nodeType 的值為8
  • nodeName 的值為‘#comment’
  • nodeValue 的值是注釋的內(nèi)容
  • parentNode 可能是 Document 或 Element
  • 不支持(沒有) 子節(jié)點

Comment 類型 于 Tetx 類型繼承自相同的基類,因此它們除 splitText() 之外的所有字符串操作方法。于Text類型相似,也可以通過 nodeValue 或 data 屬性來取得注釋的內(nèi)容。注釋節(jié)點可以通過父節(jié)點來訪問。

<div id="div"><!-- A Comment --></div>

通過以下代碼 來訪問注釋節(jié)點

var div = document.getElementById('div')
var comment = div.firstChild
// A Comment  
console.log(comment.nodeValue) // 或者 comment.data

此外使用 document.createComment() 并為其傳遞注釋文本也可以創(chuàng)建注釋節(jié)點。


一般來說,很少會創(chuàng)建和訪問注釋節(jié)點,因為注釋節(jié)點對算法鮮有影響。此外,瀏覽器也不會識別位于</html>標簽后面的注釋。

1.6、 CDATASection 類型

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

  • nodeType 的值為4
  • nodeName 的值為 ”#cdata-section“
  • nodeValue 的值是 CDATA 區(qū)域中的內(nèi)容
  • parentNode 可能是 Document 或 Element
  • 不自持(沒有)子節(jié)點

CDATA區(qū)域只會出現(xiàn)在XML文檔中,因此多數(shù)瀏覽器會把CDATA區(qū)域錯誤地解析為 Comment 或 Element。

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

這個例子中,<div>元素 應該包含一個 CDATASection 節(jié)點??墒牵拇笾髁鳛g覽器無一能夠這樣解析它。即使對于有效的XHTML 頁面,瀏覽器也沒有正確地支持嵌入式的CDATA區(qū)域。
在真正的XML文檔中,可以使用document.createDataSection() 來創(chuàng)建CDATA區(qū)域,只需要為其傳入節(jié)點內(nèi)容即可

1.7、 DocumentType 類型

DocumentType類型在Web瀏覽器中比不常用,F(xiàn)irefox、Safari、Opera、Chrome4.0+、IE9+支持這個類型,它具有以下特征:

  • nodeType 的值為 10
  • nodeName 的值為doctype的名稱
  • nodeValue 的值為 null
  • parentNode 是Document
  • 不支持(沒有)子節(jié)點。

DOM1級描述了 DocumentType對象的三個屬性:name、entities、notations。
name 表示文檔的名稱;entities 是由文檔類型描述的實體的 NamedNodeMap對象;notations 是由文檔類型描述的符號的 NamedNodeMap對象

以 h5 的文檔類型聲明為例:

<!DOCTYPE html>

DocumentType 的name 屬性中 保存的就是 HTML

console.log(DocumentType.name) // html

IE8及更早版本不支持 DocumentType,document.doctype 的值為null

1.8、 DocumentFragment

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

  • nodeType 的值為11
  • nodeName 的值為 ”#document-fragment“
  • nodeValue 的值為null
  • parentNode 的值為 null
  • 子節(jié)點可以是 Element、ProcessingInstruction、Comment、Text、CDATASection、EntityReference。

需要注意的是:不能把文檔片段直接添加到文檔中去,可以將它作為一個”倉庫“來使用,創(chuàng)建文檔片段,可以使用 document.createDocumentFragment() 方法
文檔片段繼承了 Node 的所有方法,同樣用于指向那么針對文檔的DOM操作??梢酝ㄟ^appendChild() 和 insertBefore() 將文檔片段中內(nèi)容添加到文檔中。將文檔片段作為參數(shù)傳遞給這兩個方法時,只會將文檔片段的所有子節(jié)點添加到相應的位置上;文檔片段本身永遠不會成為文檔樹的一部分。

<ul id="list"></ul>

假設我們想為這個 <ul> 元素添加3給列表項,如果逐個添加,會導致瀏覽器反復渲染(降低性能)??梢韵裣旅孢@樣使用一個文檔片段來保存創(chuàng)建的列表項,然后再一次性將他們添加到文檔中

var fragment = document.createDocumentFragment()
var ul = document.getElementById('list')
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)

1.9、 Attr 類型

元素的特性再DOM中以 Attr 類型表示。特性就是存在于 元素的 attributes 屬性中的節(jié)點。特性節(jié)點具有下列特征:

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

盡管它們也是節(jié)點,但特性卻不被認為是 DOM 文檔樹的一部分。開發(fā)人員最常用的是 getAttribute()、setAttribute() 和 removeAttribute()方法,很少直接引用特性節(jié)點。


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

使用 document.createAttribute(attr) 傳入特性的名稱可以創(chuàng)建新的特性節(jié)點。如下:

// 創(chuàng)建一個 size 特性節(jié)點
var size = document.createAttribute('size')
size.value = '20' // 賦值
// 添加到文檔中
document.getElementById('div').setAttributeNode(size)

console.log(document.getElementById('div').attributes['size'].nodeValue) // 20

將新創(chuàng)建的特性添加到 元素中,必須使用元素的 setAttributeNode(AttrNode)

二、DOM 操作技術

很多時候,DOM操作都比較間明,因此用JavaScript生成那些通常原本是用來HTML代碼生成的內(nèi)容不麻煩。不過,由于瀏覽器中充斥著隱藏的陷阱和不兼容的問題,用JavaScript處理DOM的某些部分要比處理其他部分更復雜一些。

2.1、 動態(tài)腳本

指在頁面加載是不存在,但將來的某一時刻通過修改DOM動態(tài)腳本。跟操作HTML元素一樣,創(chuàng)建動態(tài)腳本也有兩種方式:插入外部文件、直接插入JavaScript代碼。

插入外部文件

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

// 
loadScript('./client.js')

行內(nèi)方式

var script = document.createElement('script')
script.type = 'text/javascript'
script.appendChild(document.createTextNode('alert("hi")'))
document.body.appendChild(script)

在IE(8及以下版本中)上述代碼會導致錯誤。IE會將<script>視為一個特殊的元素,不允許DOM訪問其子節(jié)點。不過可以使用 <script>元素的 text屬性來指定 JavaScript 代碼。

script.text = 'alert("hi")'

然而在 Safari3.0 之前卻不支持 text 屬性。如下兼容寫法

function loadScriptString(code) {
  var script = document.createElement('script')
  script.type = 'text/javascript'
  try {
    script.appendChild(document.createTextNode(code))
  }catch(e) {
    script.text = code
  }
  document.body.appendChild(script)
}
loadScriptString('alert("Hi")')

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

2.2、動態(tài)樣式

能夠把 CSS 樣式包含到 HTML 頁面中的元素有兩個。其中,<link>元素用于包含來自外部的文件;而 <style>元素用于指定潛入的樣式。

創(chuàng)建<link>

function loadStyles(url) {
  var link = document.createElement('link')
  link.rel = 'stylesheet'
  link.type = 'text/css'
  link.href = url
  document.getElementsByTagName('head')[0].appendChild(link)
}

loadstyles('./style.css')

使用<style>元素來包含嵌入式的 CSS
與<script> 標簽類似,在IE(8及以下)中會將<style>視為一個特殊的、與<script>類似的節(jié)點,不允許訪問其子節(jié)點??梢?strong>通過 訪問元素 的 styleSheet 屬性的 cssText 屬性,賦值 css 代碼。以下是兼容寫法

function loadStylesString(css) {
  var style = document.createElement('style')
  style.type = 'text/css'
  try {
    style.appendChild(document.createTextNode(css))
  } catch(e) {
    style.styleSheet.cssText = css
  }
document.getElementsByTagName('head')[0].appendChild(style)
}

loadStylesString('body{background: red;}')

2.3 操作表格

<table>元素是 HTML中最復雜的結(jié)構(gòu)之一。相較于使用DOM核心方法動態(tài)創(chuàng)建表格的方式(createElement、createTextNode、appendChild這些Api)。HTMLDOM為 <table>、<tbody>、<tr>元素添加了一些屬性和方法。


<table>元素的屬性和方法:

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

為<tr>元素添加的屬性和方法如下:

  • cells:保存在<tr>元素中單元格的 HTMLCollection
  • deleteCell(pos):刪除指定位置的單元格
  • insertCell(pos):向cells集合中的指定位置插入一個單元格,返回新單元格的引用。
<table border="1" width="100%">
  <tbody>
    <tr>
      <td>Cell 1,1 </td>
      <td>Cell 1,2 </td>
    </tr>
    <tr>
      <td>Cell 1,2</td>
      <td>Cell 2,2</td>
    </tr>
  </tbody>
</table>

創(chuàng)建如上表格

// 創(chuàng)建 table
var table = document.createElement('table')
table.border = 1
table.width = '100%'

// 創(chuàng)建 tbody
var tbody = document.createElement('tbody')
table.appendChild(tbody)

// 創(chuàng)建第一行
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 1,2'))

// 創(chuàng)建第二行 
tbody.insertRow(1) // 指定位置 插入行
tbody.rows[1].insertCell(0) // 指定位置插入單元格
tbody.rows[1].cells[0].appendChild(document.createTextNode('Cell 1,1'))
tbody.rows[1].insertCell(1) // 插入第二個單元格
tbody.rows[1].cells[1].appendChild(document.createTextNode('cell 1,2'))

// 將表格添加到文檔中
document.body.appendChild(table)

使用這些屬性和方法創(chuàng)建表格的邏輯性更強,也容易看懂。

2.4 使用 NodeList

理解 NodeList 及其 ”近親“ NamedNodeMap 和 HTMLCollection,是從整體上透徹理解ODM的關鍵所在。這三個集合都是”動態(tài)的“;每當文檔結(jié)構(gòu)發(fā)送變化時,它們都會得到更新。始終保存著最新、最準確的信息。從本質(zhì)上說,所有 NodeList 對象都是在 訪問 DOM 文檔時 時時運行的查詢。

例如下面代碼會導致無限循環(huán)

var divs = document.getElementsByTagName('div') // 動態(tài)集合,實時跟新
  , 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')
  div.appendChild(document.createTextNode(i))
  document.body.appendChild(div)
}

一般來說,應該盡量減少訪問 NodeList的次數(shù)。因為每次訪問 NodeList,都會運行依次基于全文檔的查詢。所以,可以考慮將從 NodeList 中 取得的值緩存起來。

小結(jié)

DOM 是語言中立的API,用于訪問和操作 HTML 和 XML文檔。DOM1級將它們形象的看出一個層次化的節(jié)點數(shù),可以使用JavaScript來操作這個節(jié)點樹,進而改變底層文檔的外觀和結(jié)構(gòu)。
DOM由各種節(jié)點構(gòu)成,簡要總結(jié)如下

  • 最基本的節(jié)點類型是 Node,用于抽象地表示文檔中一個獨立的部分;所有其他類型都繼承子Node
  • Document 類型表示整個文檔,是一組分層節(jié)點的根節(jié)點。在JavaScript中,document對象是Document的一個實例。使用document對象,有很多種方式可以查詢和取得節(jié)點。
  • Element 節(jié)點表示文檔中的所有HTML 或 XML 元素,有很多種方式可以查詢和取得節(jié)點。
  • 另外還有一些節(jié)點類型,分別表示文本內(nèi)容、注釋、文檔類型、CDATA區(qū)域、文檔片段。

理解DOM的關鍵,就是理解DOM對性能的影響。DOM操作往往是JavaScript程序中開銷最大的部分,而因訪問 NodeList 導致的問題為最多。NodeList對象都是”動態(tài)的“,這就意味著每次訪問 NodeList對象,都會運行一次查詢。有鑒于此,最好的辦法就是盡量減少DOM的操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • ??DOM(文檔對象模型)是針對 HTML 和 XML 文檔的一個 API(應用程序編程接口)。 ??DOM 描繪...
    霜天曉閱讀 3,866評論 0 7
  • ??DOM 1 級主要定義的是 HTML 和 XML 文檔的底層結(jié)構(gòu)。 ??DOM2 和 DOM3 級則在這個結(jié)構(gòu)...
    霜天曉閱讀 1,599評論 1 3
  • 參考書:《JavaScript高級程序設計》 知識點前提:什么是節(jié)點 Node類型 DOM1級定義了一個Node接...
    DHFE閱讀 484評論 0 0
  • 本章內(nèi)容 理解包含不同層次節(jié)點的 DOM 使用不同的節(jié)點類型 克服瀏覽器兼容性問題及各種陷阱 DOM 是針對 HT...
    悶油瓶小張閱讀 777評論 0 1
  • 之前通過深入學習DOM的相關知識,看了慕課網(wǎng)DOM探索之基礎詳解篇這個視頻(在最近看第三遍的時候,準備記錄一點東西...
    微醺歲月閱讀 4,761評論 2 61

友情鏈接更多精彩內(nèi)容