本章內(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é)點的 firstChild 和 lastChild 分別指向其 childNodes中 第一個和最后一個子節(jié)點。其中 firstChild = childNodes[0];lastChild = childNodes[childNodes.length - 1]明確這些關系能夠?qū)ξ覀儾檎液驮L問文檔結(jié)構(gòu)中的節(jié)點提高極大的便利。如圖 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的操作。