DOM 是也叫文檔對象模型,是 HTML 和 XML 文檔的一個(gè) API肋乍,其描述了一個(gè)層次節(jié)點(diǎn)樹,允許開發(fā)人員對文檔樹進(jìn)行操作敷存。
Node 接口
DOM 樹是由一個(gè)一個(gè)的節(jié)點(diǎn)構(gòu)成的墓造,DOM1 級中定義了一個(gè) Node 接口,這個(gè)接口會(huì)由頁面中的一個(gè)個(gè)節(jié)點(diǎn)去實(shí)現(xiàn)锚烦。
在 JavaScript 中觅闽,Node 是一個(gè)內(nèi)置對象:
Node
// ? Node() { [native code] }
Node 節(jié)點(diǎn)共分為 12 種類型,每一種類型都由 Node 對象上的一些常量來表示:
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
JavaScript 中每個(gè)節(jié)點(diǎn)對象都有一個(gè) nodeType
屬性挽牢,該屬性的值對應(yīng)著上面的 12 個(gè)常量:
document.nodeType //9
document.body.nodeType //1
注:由于 IE 中的所有 DOM 對象都是以 COM 對象呈現(xiàn)的谱煤,并且 IE 沒有公開 Node 對象的構(gòu)造函數(shù),因此在 IE 中使用 Node 對象上的節(jié)點(diǎn)類型常量可能會(huì)發(fā)生錯(cuò)誤禽拔,因此最好使用節(jié)點(diǎn)對象的 nodeType
屬性,以保證兼容性。
注(2017-08-17):我測試了一下仇味,在 IE9 以下訪問 Node 構(gòu)造函數(shù)會(huì)拋出錯(cuò)誤,在 IE9 以上可以正常使用 Node 的構(gòu)造函數(shù)茧痕。
nodeName 和 nodeValue
節(jié)點(diǎn)的 nodeName
屬性表示元素的標(biāo)簽名,nodeValue
屬性表示元素的節(jié)點(diǎn)值恼除。對于元素節(jié)點(diǎn) nodeValue
屬性將返回 null
踪旷。
document.body.nodeName // "BODY"
document.body.nodeValue // null
childNodes
每個(gè)節(jié)點(diǎn)都有一個(gè) childNodes
屬性,里面保存了一個(gè) NodeList 對象豁辉,該對象是一個(gè)類數(shù)組對象令野,用來保存一組有序節(jié)點(diǎn)。
該對象是一個(gè)動(dòng)態(tài)查詢的結(jié)果徽级,因此節(jié)點(diǎn)的變化將會(huì)自動(dòng)在 NodeList 對象中更新气破。該對象有個(gè) length
屬性,表示對象中保存的節(jié)點(diǎn)數(shù)量餐抢,同樣现使,每當(dāng)節(jié)點(diǎn)發(fā)生變化時(shí),該屬性也會(huì)自動(dòng)變化旷痕。
let div = document.createElement("div")
let nodeLists = document.body.childNodes //[]
document.body.appendChild(div)
nodeLists // [div]
nodeList.length // 1
document.body.removeChild(div)
nodeList // [div]
nodeList.length // 1
上面的結(jié)果表明:節(jié)點(diǎn) childNodes
屬性中保存的 NodeLists 對象是隨節(jié)點(diǎn)的變化動(dòng)態(tài)更新的碳锈。
獲取 NodeLists 中的節(jié)點(diǎn)可以使用 []
語法或者 item
方法:
const node1 = nodeLists[1]
const node2 = nodeLists.item(1)
firstChild 和 lastChild
firstChild
和 lastChild
分別指向 NodeLists 中的第一個(gè)和最后一個(gè)節(jié)點(diǎn),其值分別對應(yīng)于 NodeLists[0]
以及 NodeLists[NodeLists.length - 1]
欺抗。
注:firstChild
和 lastChild
是父節(jié)點(diǎn)的屬性售碳,而非 NodeLists 集合的屬性。
const body = document.body
body.firstChild === body.childNodes[0] // true
...
perviousSibling 和 nextSibling
每個(gè)節(jié)點(diǎn)都有一個(gè) previousSibling
和 nextSibling
屬性佩迟,分別指向其前一個(gè)和后一個(gè)兄弟節(jié)點(diǎn)团滥。如果元素沒有前一個(gè)或后一個(gè)兄弟節(jié)點(diǎn)竿屹,那么其 previousSibling
和 nextSibling
的值為 null
报强。
const body = document.body
body.previousSibling // <head>...</head>
body.nextSibling // null
hasChildNodes 和 ownerDocument
每個(gè)節(jié)點(diǎn)都一個(gè) hasChildNodes()
方法,用來檢驗(yàn)該節(jié)點(diǎn)是否有子節(jié)點(diǎn)(NodeLists 的長度是否大于 1).
const body = document.body
body.hasChildNodes() // true
同時(shí)拱燃,每個(gè)節(jié)點(diǎn)都擁有一個(gè) ownerDocument
屬性秉溉,該屬性執(zhí)行表示該文檔的節(jié)點(diǎn)(document
),這個(gè)屬性的意義表示任何節(jié)點(diǎn)都屬于其所在的文檔碗誉。
const body = document.body
body.ownerDocument === document // true
操作節(jié)點(diǎn)
1.appendChild
該方法用來想父元素的 NodeLists 集合中追加元素召嘶,接受一個(gè) Node 對象作為參數(shù)。調(diào)用該方法返回被追加的節(jié)點(diǎn)哮缺。
const firstDiv = docuemnt.createElement("div")
const body = document.body
body.appendChild( firstDiv )
body.childNodes // [div]
另外弄跌,由于相同的節(jié)點(diǎn)不能出現(xiàn)在 DOM 樹的多個(gè)位置中,因此這里如果重復(fù)追加 firstDiv
尝苇,NodeLists 中的子節(jié)點(diǎn)數(shù)目不變:
body.appendChild( firstDiv )
body.appendChild( firstDiv )
body.appendChild( firstDiv )
body.childNodes // [div]
同時(shí)铛只,如果將已存在的子節(jié)點(diǎn)追加到 NodeLists 中埠胖,其將會(huì)被挪動(dòng)到 NodeLists 集合的最后一項(xiàng),其之后的節(jié)點(diǎn)將會(huì)頂替該節(jié)點(diǎn)先前的位置淳玩,這個(gè)過程同樣遵循“同一個(gè)節(jié)點(diǎn)不能出現(xiàn) DOM 樹的多個(gè)位置中”這一原則直撤。
如果傳入到 appendChild()中的節(jié)點(diǎn)已經(jīng)是文檔的一部分了,那結(jié)果就是將該節(jié)點(diǎn)從原來的位置
轉(zhuǎn)移到新位置蜕着。即使可以將 DOM 樹看成是由一系列指針連接起來的谋竖,但任何 DOM 節(jié)點(diǎn)也不能同時(shí)出
現(xiàn)在文檔中的多個(gè)位置上。因此承匣,如果在調(diào)用 appendChild()時(shí)傳入了父節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn)蓖乘,那么
該節(jié)點(diǎn)就會(huì)成為父節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)。
上面引用自 《JavaScript 高級程序設(shè)計(jì)(第三版)》韧骗。
2.insertBefore
同 appendChild
驱敲,該方法同樣也由父節(jié)點(diǎn)調(diào)用,該方法接受兩個(gè)參數(shù):
- 待插入的節(jié)點(diǎn)
- 參照節(jié)點(diǎn)
調(diào)用該方法時(shí)宽闲,將會(huì)把帶插入的節(jié)點(diǎn)插入到參照節(jié)點(diǎn)之前众眨,如果該方法的最后一個(gè)參數(shù)為 null
,那個(gè)調(diào)用這個(gè)方法的表現(xiàn)和 appendChild
相同容诬,都是將節(jié)點(diǎn)追加到 NodeLists 集合的末尾娩梨。調(diào)用該方法返回被插入的節(jié)點(diǎn)。
const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.insertBefore(p,div)
body.childNodes // [p, div]
同樣览徒,該方法遵循“同一個(gè)節(jié)點(diǎn)不能出現(xiàn) DOM 樹的多個(gè)位置中”原則:
body.insertBefore(p,null)
body.childNodes // [div, p]
3.replaceChild
該方法用來對節(jié)點(diǎn)進(jìn)行替換狈定,接受兩個(gè)參數(shù):
- 新節(jié)點(diǎn)
- 被替換的節(jié)點(diǎn)
調(diào)用這個(gè)方法時(shí),被替換的節(jié)點(diǎn)將被移除习蓬,并由新的節(jié)點(diǎn)代替:
const div = document.createElement("div")
const p = document.createElement("p")
const a = document.createElement("a")
const body = document.body
body.appendChild(div)
body.appendChild(p)
body.childNodes // [div, p]
// 替換節(jié)點(diǎn)
body.replaceChild(a,body.firstChild)
body.childNodes // [a, p]
4.removeChild
如果只想從文檔樹中移除某個(gè)節(jié)點(diǎn)纽什,需要調(diào)用 removeChild
方法,該方法接受需要被移除的節(jié)點(diǎn)作為參數(shù)躲叼,返回被移除的節(jié)點(diǎn):
const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.appendChild(p)
body.childNodes // [div, p]
// 移除節(jié)點(diǎn)
body.removeChild(p) // p
body.childNodes // [div]
5.cloneNode
該方法用來對節(jié)點(diǎn)進(jìn)行復(fù)制芦缰,接受一個(gè)標(biāo)志參數(shù) true
或者 false
,表示是否執(zhí)行深復(fù)制枫慷,當(dāng)執(zhí)行深復(fù)制時(shí)让蕾,會(huì)復(fù)制節(jié)點(diǎn)本身以及其所有的子節(jié)點(diǎn)。當(dāng)進(jìn)行淺復(fù)制時(shí)或听,僅復(fù)制節(jié)點(diǎn)本身探孝。
const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.appendChild(p)
// 進(jìn)行淺復(fù)制
body_shallowcpy = body.cloneNode()
// 進(jìn)行深復(fù)制
body_deepcpy = body.cloneNode(true)
body_shallowcpy .childNodes // []
body_deepcpy .childNodes // [div, p]
總結(jié)
本文主要介紹了 Node 類型以及操作 DOM 的幾個(gè)方法∮桑總結(jié)一下:
- Node 是一個(gè)接口顿颅,規(guī)定了 DOM 節(jié)點(diǎn)的一些實(shí)現(xiàn)
- 所有的節(jié)點(diǎn)都會(huì)實(shí)現(xiàn) Node 接口,每個(gè)節(jié)點(diǎn)都有一個(gè)
nodeType
屬性足丢,和 Node 對象中的一些常量一一對應(yīng)粱腻,表示該節(jié)點(diǎn)的類型 - 由于 IE 的 DOM 不是使用原生 JavaScript 實(shí)現(xiàn)的绍填,因此無法獲取到 Node 對象上的這些常量,因此最好使用數(shù)字值進(jìn)行節(jié)點(diǎn)類型的判斷栖疑,以提高兼容性
-
nodeName
和nodeValue
屬性 -
childNodes
是一個(gè)包含了元素所有子節(jié)點(diǎn)的一個(gè)集合讨永,該集合是動(dòng)態(tài)的,DOM 樹中的任何變化都會(huì)反映到這個(gè)集合中 -
firstChild
和lastChild
-
previousSibling
和nextSibling
-
hasChildNodes()
可以用來檢驗(yàn)元素是否有子節(jié)點(diǎn) - 每個(gè)節(jié)點(diǎn)元素都擁有一個(gè)
ownerDocument
屬性遇革,指向文檔節(jié)點(diǎn)卿闹,該屬性的意義表示任何節(jié)點(diǎn)都屬于其所在的文檔 - 操作節(jié)點(diǎn)的幾個(gè)方法
-
cloneNode()
方法用來復(fù)制節(jié)點(diǎn),有深復(fù)制和淺復(fù)制之分
使用 JavaScript 操作 DOM 可以讓網(wǎng)頁出現(xiàn)多種多樣的變化萝快,JavaScript 中與 DOM 相關(guān)的方法很多很多锻霎,也是使大多數(shù)前端er 們感到陌生或者似懂非懂的地方(正經(jīng)臉:)),因?yàn)槲覀兤綍r(shí)寫代碼時(shí)大多用的是一些 DOM 操作的封裝揪漩,比如 jQuery 庫旋恼,甚至如果你使用 Vue 或者 React 之類的框架,那操作 DOM 的機(jī)會(huì)更少了奄容。不過學(xué)習(xí)這塊的知識(shí)可以扎實(shí)我們的基礎(chǔ)冰更,雖然不一定有太多的使用機(jī)會(huì),但你可以推測這些框架或者庫是怎么實(shí)現(xiàn)這些 DOM 操作的昂勒,對技術(shù)和思維上的提升會(huì)有不少的幫助蜀细。
還是那句話:如果哪天遺忘了,請隨時(shí)回過頭來看看~
完戈盈。