前言:盡管現在有很多優秀的框架,大大簡化了我們的DOM操作,但是我們仍然要學好DOM知識來寫原生JS,從根本上去理解,才更能在解決問題時舉重若輕。
一、什么是DOM
D(document)O(object)M(model) 文檔對象模型。
DOM(文檔對象模型)是HTML和XML文檔的編程接口。它提供了對文檔的結構化的表述,并定義一種方式可以使從程序中對該結構進行訪問,從而改變文檔的結構、樣式和內容。DOM將文檔解析為一個由節點和對象(包含屬性和方法的對象)組成的結構集合。
上述說法是MDN的解釋,太官方,我們來換種說法來解釋。
DOM就是一種想象的樹形結構,它的作用是將網頁轉為一個 JavaScript 對象,從而可以用腳本進行各種操作(比如增刪內容)。
如上圖,是我們文檔的樹形結構,我們通過DOM模型將上述結構一一映射成節點(通過構造函數把頁面中的節點變成實例對象,dom就是這樣把文檔變成對象的),這些節點就又構成了節點樹,也就是我們說的想象中的那棵DOM Tree。
二、Node
DOM 的最小組成單位叫做節點(node)。文檔的樹形結構(DOM 樹),就是由各種不同類型的節點組成。
節點主要有7種類型:
-
Document
:整個文檔樹的根節點 -
Document
:doctype標簽節點,如<!DOCTYPE html>
-
Element
:網頁的各種Html
標簽,比如<body><div>
等 -
Attribute
:網頁元素的屬性 -
Text
:標簽之間或者標簽包含的文本 -
Comment
:注釋 -
DocunmentFragment
:文檔的片段
所以文檔樹對應的節點樹如同下圖:
注:DOM樹有3種層級結構:
- 父節點關系(parentNode):直接的上級節點
- 子節點關系(childNodes):直接的下級節點
- 兄弟關系(sibling):擁有同一個父節點的同級節點
值得注意的是,在上圖中只有根節點也就是<html>
對應的節點沒有父節點。
三、Node接口
瀏覽器提供一個原生的節點對象Node
,上面的7種節點均繼承了Node
,因此具有一些共同的屬性和方法。這是DOM操作的基礎。
1、屬性
1.1 Node.prototype.nodeType
nodeType
屬性返回一個整數值,表示節點的類型。
如上圖,我們獲取當前頁面的body
標簽下的第一個孩子,是一個div
標簽,然后我們通過nodeType
來看一下它的節點類型,結果返回了一個數字1,這代表著是一個Element
節點。
【至于為什么會是返回一個數字而不是簡單明了的返回Element
,這也是由于歷史原因,早期計算機內存緊張,為了節省內存使用了并無規律的數字】
常見的有以下:
Node.ELEMENT_NODE
:1
Node.ATTRIBUTE_NODE
:2
Node.TEXT_NODE
:3
Node.COMMENT_NODE
:8
Node.DOCUMENT_NODE
:9
Node.DOCUMENT_TYPE_NODE
:10
Node.DOCUMENT_FRAGMENT_NODE
:11
1.2 Node.prototype.nodeName
nodeName
屬性返回節點名稱
注:在元素節點中,返回名稱基本都是大寫,只有<svg>
標簽返回的是小寫。
1.3 Node.prototype.nodeValue
nodeValue
屬性返回一個字符串,表示當前節點本身的文本值,該屬性可讀寫。
只有文本節點(text)、注釋節點(comment)和屬性節點(attr)有文本值,因此這三類節點的nodeValue
可以返回結果,其他類型的節點一律返回null
。
// html 代碼如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue //"hello world"
1.4 Node.prototype.textContent
textContent
返回節點及后代節點的文本 ,即獲取文本
這里和innerText
一起講:
早期并沒有獲取文本的API ,導致編碼很繁瑣,所以后來IE自己添加了一個API就是innerText
,然后火狐和opera也推出了textContent
兩者的區別:
-
textContent
會獲取所有元素的內容,包括<script>
和<style>
元素,然而innerText
不會獲取這些內容。 -
innerText
可以意識到樣式,它不會返回樣式為display:none
也就是隱藏的文本,而textContent
會。 - 由于
innerText
會意識到樣式,也就是會受樣式的影響,因此會觸發重排(reflow)導致性能低,而textContent
不會。 - 與
textContent
不同, 在 Internet Explorer (對于小于等于 IE11 的版本) 中對innerText
進行修改, 不僅會移除當前元素的子節點,而且還會永久性地破壞所有后代文本節點(所以不可能將節點再次插入到任何其他元素或同一元素中).
1.5 Node.prototype.nextSibling
Node.nextSibling
屬性返回緊跟在當前節點后面的第一個同級節點。如果當前節點后面沒有同級節點,則返回null
。
值得注意的是,該屬性還包括文本節點和注釋節點(``)。因此如果當前節點后面有空格或者回車,該屬性會返回一個文本節點,內容為空格或回車。
document.body.nextSibling
返回了文本節點或者注釋節點,而我們需要獲得是元素節點, 也可以用document.prototype.nextElementSibling
直接獲取該節點后面最接近的同級元素節點。
1.6 Node.prototype.previousSibling
previousSibling
屬性返回當前節點前面的、距離最近的一個同級節點。如果當前節點前面沒有同級節點,則返回null
。
Node.prototype.previousElementSibling
前一個同級元素節點
其他同1.5 一致
1.7 Node.prototype.firstChild,Node.prototype.firstElementChild
firstChild
屬性返回當前節點的第一個子節點,如果當前節點沒有子節點,則返回null
。
firstElementChild
屬性返回當前節點的第一個元素節點。
1.8 Node.prototype.lastChild,Node.prototype.lastElementChild
lastChild
屬性返回當前節點的最后一個子節點,如果當前節點沒有子節點,則返回null
。
lastElementChild
屬性返回當前節點的最后一個元素節點。
1.9 Node.prototype.childNodes
childNodes
屬性返回一個類似數組的對象(NodeList
集合),成員包括當前節點的所有子節點。
值得注意的是,除了元素節點,childNodes
屬性的返回值還包括文本節點和注釋節點。如果當前節點不包括任何子節點,則返回一個空的NodeList
集合。由于NodeList
對象是一個動態集合,一旦子節點發生變化,立刻會反映在返回結果之中。
1.10 Node.prototype.children
children
屬性返回一個類似數組的對象(HTMLCollection),成員包括當前節點的所有子元素節點。
值得注意的是,這里就不會返回文本節點和注釋節點了,它只會返回元素節點。由于HTMLCollection
集合是動態集合,一旦子節點發生變化,立刻會反映在返回結果之中。
小tips : Nodelist
和 HTMLCollection
集合的區別
-
NodeList
可以包含各種類型的節點,HTMLCollection
只能包含 HTML 元素節點. -
NodeList
實例可能是動態集合,也可能是靜態集合。目前,只有Node.childNodes
返回的是一個動態集合,其他的NodeList
都是靜態集合。而HTMLCollection
實例都是動態集合,節點的變化會實時反映在集合中. - 與
NodeList
接口不同,HTMLCollection
沒有forEach
方法,只能使用for
循環遍歷。
2、方法
2.1 Node.prototype.appendChild()
appendChild
方法就是接受一個節點對象作為參數,將其作為最后一個子節點,插入到當前節點 。該方法的返回值就是插入的子節點。
注意:
1、如果參數節點是 DOM 已經存在的節點,appendChild
方法會將其從原來的位置移動到新位置。
2、如果appendChild
方法的參數是DocumentFragment
節點,那么插入的是DocumentFragment
的所有子節點,而不是DocumentFragment
節點本身。返回值是一個空的DocumentFragment
.
2.2 Node.prototype.hasChildNodes()
hasChildNodes
方法返回一個布爾值,表示當前節點是否有子節點。
注意:子節點包括所有類型的節點,并不僅僅是元素節點。哪怕節點只包含一個空格,hasChildNodes
方法也會返回true
判斷一個節點是否有子節點,有以下3種方法:
1、node.hasChildNodes()
2、node.firstChild ! == null
3、node.childNodes && node.childNodes.length > 0
2.3 Node.prototype.cloneNode()
cloneNode
方法拷貝一個節點,并且可以接受一個布爾值,來表示是否同時拷貝子節點。它的返回值是一個克隆出來的新節點。
深拷貝:深入進去全部拷貝,包括子節點。
淺拷貝:只拷貝節點本身。
值得注意的是,該方法返回的節點不在文檔中,無任何父節點,必須用如appendChild()
等方法添加。
2.4 Node.prototype.insertBefore()
insertBefore
方法用于將某個節點插入父節點內部的指定位置。
insertBefore
方法接受兩個參數,第一個參數是所要插入的節點newNode
,第二個參數時父節點parentNode
內部的一個子節點referenceNode
。newNode
將插在referenceNode
這個子節點的前面。返回值是插入的新節點newNode
.
2.5 Node.prototype.removeChild()
removeChild
方法接受一個子節點作為參數,用于從當前節點移除該子節點。返回值是移除的子節點。
值得注意的是,被移除的節點依然存在于內存之中,但不再是DOM的一部分。所以,一個節點移除以后,依然可以使用它,比如插入到另一個節點下面。
如果參數節點不是當前節點的子節點,removeChild
方法將報錯。
2.6 Node.prototype.replaceChild()
replaceChild
方法用于將一個新的節點,替換當前節點的某一個子節點。
創造一個新兒子取代掉舊兒子,舊兒子去哪兒了?舊兒子去內存了。
2.7 Node.prototype.contains()
contains
方法返回一個布爾值,表示參數節點是否為該節點的后代節點。
2.8 Node.prototype.isEqualNode(),Node.prototype.isSameNode()
isEqualNode
方法返回一個布爾值,用于檢查兩個節點是否相等。所謂相等的節點,指的是兩個節點的類型相同、屬性相同、子節點相同。
isSameNode
方法返回一個布爾值,表示兩個節點是否為同一個節點。
image
所以說,isSameNode
就等同于 ===
嚴格相等運算符。
2.9 Node.prototype.normalize()
normailize
方法用于清理當前節點內部的所有文本節點(text)。它會去除空的文本節點,并且將毗鄰的文本節點合并成一個,也就是說不存在空的文本節點,以及毗鄰的文本節點
四、Document接口
document
節點對象代表整個文檔,每張網頁都有自己的document
對象。window.document
屬性就指向這個對象。只要瀏覽器開始載入 HTML 文檔,該對象就存在了,可以直接使用。
document
對象有不同的辦法可以獲取。
1、屬性
1.1 用于指向其他節點(快捷獲取某些特殊節點)的屬性
-
document.doctype
指向<DOCTYPE>
節點,即文檔類型節點。 -
document.documentElement
指向 DOM 的html
節點 -
document.activeElement
指向獲得焦點的那個節點 -
document.fullscreenElement
返回當前以全屏狀態展示的 DOM 元素。 -
document.body
指向<body>
節點 -
document.head
指向<head>
節點。 -
document.scrollingElement
返回文檔的滾動元素。 - ……
1.2 返回文檔特定元素的偽數組集合的屬性
-
document.links
屬性返回當前文檔所有設定了href
屬性的<a>
及<area>
節點。 -
document.forms
屬性返回所有<form>
表單節點。 -
document.images
屬性返回頁面所有<img>
圖片節點。 -
document.scripts
屬性返回所有<script>
節點。 -
document.styleSheets
屬性返回文檔內嵌或引入的樣式表集合 - ……
以上均為動態集合,而且除了document.styleSheets
,以上的集合屬性返回的都是HTMLCollection
實例。
1.3 返回文檔信息的屬性
-
document.documentURI和document.URL
屬性都返回一個字符串,表示當前文檔的網址。 -
document.domain
屬性返回當前文檔的域名,不包含協議和接口。 -
document.Location
對象是瀏覽器提供的原生對象,提供 URL 相關的信息和操作方法。 -
document.title
屬性返回當前文檔的標題。 -
document.characterSet
屬性返回當前文檔的編碼,比如UTF-8
-
document.referrer
屬性返回一個字符串,表示當前文檔的訪問者來自哪里. - ……
1.4 返回文檔狀態的屬性
-
document.hidden
屬性返回一個布爾值,表示當前頁面是否可見。 -
document.visibilityState
返回文檔的可見狀態。 - ……
2、方法
-
document.open
方法清除當前文檔所有內容,使得文檔處于可寫狀態,供document.write
方法寫入內容。 -
document.close
方法用來關閉document.open()
打開的文檔。 -
document.write
方法用于向當前文檔寫入內容。 -
document.writeln
方法與write
方法完全一致,除了會在輸出內容的尾部添加換行符。 -
document.querySelector
接受一個 CSS 選擇器作為參數,返回匹配該選擇器的元素節點 -
document.querySelectorAll
方法與querySelector
用法類似,區別是返回一個NodeList
對象,包含所有匹配給定選擇器的節點。 -
document.getElementsByTagName
搜索 HTML 標簽名,返回符合條件的元素。 -
document.getElementsByClassName
返回一個類似數組的對象(HTMLCollection
實例),包括了所有class
名字符合指定條件的元素,元素的變化實時反映在返回結果中。 -
document.getElementsByName
方法用于選擇擁有name
屬性的 HTML 元素,返回一個類似數組的的對象(NodeList
實例). -
document.getElementById
方法返回匹配指定id
屬性的元素節點 -
document.createElement
方法用來生成元素節點,并返回該節點。 -
document.createTextNode
方法用來生成文本節點(Text
實例) -
document.createDocumentFragment
方法生成一個空的文檔片段對象(DocumentFragment
實例)。 -
document.hasFocus
方法返回一個布爾值,表示當前文檔之中是否有元素被激活或獲得焦點。 - ……
五、Element接口
1、屬性
-
Element.id
屬性返回指定元素的id
屬性,該屬性可讀寫。 -
Element.tagName
屬性返回指定元素的大寫標簽名 -
Element.title
屬性用來讀寫當前元素的 HTML 屬性title
-
Element.attributes
屬性返回一個類似數組的對象,成員是當前元素節點的所有屬性節點 -
Element.className
屬性用來讀寫當前元素節點的class
屬性。 -
Element.classList
返回一個偽數組,成員是當前元素節點的每個class
。 -
Element.innerHTML
屬性返回一個字符串,等同于該元素包含的所有 HTML 代碼。 -
Element.clientHeight
屬性返回一個整數值,表示元素節點的 CSS 高度 -
Element.clientWidth
屬性返回元素節點的 CSS 寬度,同樣只對塊級元素 -
Element.scrollHeight
屬性返回一個整數值(小數會四舍五入),表示當前元素的總高度(單位像素),包括溢出容器、當前不可見的部分。 -
Element.scrollWidth
屬性表示當前元素的總寬度(單位像素),其他地方都與scrollHeight
屬性類似。 -
Element.children
屬性返回一個類似數組的對象(HTMLCollection
實例),包括當前元素節點的所有子元素。 -
Element.childElementCount
屬性返回當前元素節點包含的子元素節點的個數,與Element.children.length
的值相同。 - ……
2、方法
-
getAttribute()
讀取某個屬性的值 -
getAttributeNames()
返回當前元素的所有屬性名 -
setAttribute()
寫入屬性值 -
hasAttribute()
某個屬性是否存在 -
hasAttributes()
當前元素是否有屬性 -
removeAttribute()
刪除屬性 -
Element.querySelector
接受 CSS 選擇器作為參數,返回父元素的第一個匹配的子元素。 -
Element.querySelectorAll
接受 CSS 選擇器作為參數,返回一個NodeList
實例,包含所有匹配的子元素。 -
Element.remove
方法繼承自 ChildNode 接口,用于將當前元素節點從它的父節點移除。 -
Element.getBoundingClientRect
方法返回一個對象,提供當前元素節點的大小、位置等信息,基本上就是 CSS 盒狀模型的所有信息 -
Element.addEventListener()
:添加事件的回調函數 -
Element.removeEventListener()
:移除事件監聽函數 -
Element.dispatchEvent()
:觸發事件 - ……