JavaScript淺嚐紀錄No.7-了解一下DOM吧!

DOM是甚麼?

MDN的介紹:

The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects. That way, programming languages can connect to the page.

The DOM is not a programming language, but without it, the JavaScript language wouldn’t have any model or notion of web pages, HTML documents, XML documents, and their component parts (e.g. elements). Every element can all be accessed and manipulated using the DOM and a scripting language like JavaScript.

HTML跟CSS是如何透過瀏覽器去渲染網頁的解釋可以參考Umar Hansa-An Introduction to Browser Rendering

我自己是這麼理解DOM的:HTML經過瀏覽器作用之後產生的一種樹狀結構,就是DOM,而其他程式語言(如JavaScript,以下都稱JavaScript)可以操控這個樹狀結構來變更原本儲存的資料內容(原本樹狀結構的內容會因此改變),而經過JavaScript與原本DOM互相作用後,瀏覽器就會將這個被JavaScript操控後的DOM,由上到下的渲染成網頁。
而在這個樹狀結構中,每筆儲存的資料都稱作節點(Node),每個節點都是一個物件,並且JavaScript也是透過節點來操控DOM,節點有4種類型:

  1. 元素節點 (Element Node),HTML每個標籤,如<div><h1><p>…。

  2. 文字節點 (Text Node),HTML每個標籤包住的文字,如標題一內文…。

  3. 屬性節點 (Attribute Node),HTML每個標籤內有的屬性,如classdata-*type

  4. 註解節點 (Comment Node),HTML的註解,如<!--註解內容-->

樹狀結構是甚麼?它是一種儲存資料的方式,它有很明確的上下階層的關係,不同的節點是可以從共同連結的元素開始去尋找的,就像一棵樹一樣,每片樹葉可以透過相同連接的樹枝去尋找。
Alpha Camp學期2-1-樹狀結構與 DOM 節點

JavaScript淺嚐紀錄No.4-Array資料結構處理及應用中有提到其他資料結構可以參考看看。

題外話:很常聽到的BOM又是甚麼?
BOM(Browser Object Model),是瀏覽器提供的物件,程式語言(如JavaScript)可以透過這個物件去操控瀏覽器,目前瀏覽器所提供的物件包含window(是BOM的根節點)、document(是DOM的根節點)、location、navigator、screen、history。

Happy Coding-JavaScript入門系列:BOM和DOM筆記解釋圖

BOM更詳細的解釋請參考
Fooish程式技術-JavaScript 瀏覽器物件模型 (BOM - Browser Object Model)
Happy Coding-JavaScript入門系列:BOM和DOM筆記

JavaScript如何選定節點來變更DOM的內容

上面有提到DOM所儲存的每筆資料都稱作節點,而這個節點有4個類型:元素節點、文字節點、屬性節點及註解節點,因為很少去處理註解節點,所以下面就只針對其他3種來說明。

下面這段HTML是從Bootstrap-componenets-Alerts節錄下來的,並作為之後介紹用。

1
2
3
4
5
6
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">Well done!</h4>
<p>You can see how spacing within an alert works with this kind of content.</p>
<hr>
<p class="mb-0">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>
</div>

元素節點 (Element Node)

取得
通常會用一個變數儲存取得的元素節點。
利用document.querySelector('name')document.querySelectorAll('name')取得單一或多個節點。

註:document.getElementById('idName')document.getElementByClassName('className')document.getElementByTagName('tagName')還是可以使用,但因為querySelector()是利用CSS選取器來取得值的,所以寫過CSS的話,運用querySelector()querySelectorAll()會比較直覺地去選取想要的元素。

1
2
3
4
5
6
7
8
9
10
11
let alertHeading = document.querySelector('.alert-heading') //單個節點
console.log(alertHeading) //<h4 class="alert-heading">Well done!</h4>
console.log(typeof alertHeading) //object
console.log(alertHeading.nodeName) //H4
console.log(alertHeading.nodeValue) //Null,只有文字節點或是註解節點,nodeValue才會回傳值。
console.log(alertHeading.nodeType) //1
console.log(alertHeading.textContent) //Well done!,使用textContent就能抓出元素節點裡面的文字內容,但這並不是文字節點

let alertP = document.querySelectorAll('p') //多個節點
console.log(alertP) //NodeList(2) [p, p.mb-0]
console.log(alertP.nodeName) //undefined,因為是多個節點組合的NodeList,所以無法知道是要取得哪個的nodeName

運用

可以透過取得某個元素節點後查找其他元素節點。

1
2
3
4
let alertHeading = document.querySelector('.alert-heading')
console.log(alertHeading.parentElement) //<div class="alert alert-success" role="alert">...</div>
console.log(alertHeading.nextElementSibling) //<p>You...</p>
console.log(alertHeading.parentElement.lastElementChild)//<p class="mb-0">Whenever...</p>

下面圖片(該圖片擷取自Alpha Camp學期2-1遍歷周邊 DOM 節點)提供一些可以查找其他元素節點的屬性:
Alpha Camp學期2-1遍歷周邊 DOM 節點
2種查找其他元素的屬性的差別,在於前面的會把標籤間的空白也當作文字節點返回,但後面(有Element)的只會返回元素節點。
詳細的差別可以參考MDN-Node.nextSiblingMDN-Node.nextElementSibling區別,其他屬性的差別也是一樣。

文字節點 (Text Node)

取得

通常會用一個變數儲存取得的文字節點。

1
2
3
4
5
6
7
let headContent=document.querySelector('.alert-heading').firstChild
console.log(headContent)//"Well done!",這是一個文字節點
console.log(typeof headContent) //object
console.log(headContent.nodeName) //#text
console.log(headContent.nodeValue)//Well done! ,這是文字節點裡面的值
console.log(typeof headContent.nodeValue) //string
console.log(headContent.nodeType) //3

運用

typeof headContent.nodeValue顯示出來是string,也因此可以使用string的屬性及方法。

1
2
3
4
console.log(headContent.nodeValue.length)//10
console.log(headContent.nodeValue.charAt(5))//d
console.log(headContent.nodeValue.indexOf('e'))//1
console.log(headContent.nodeValue.split(' '))//["Well", "done!"]

更多string的用法,請自行參考MDN-String

屬性節點 (Attribute Node)

取得

目前會使用其他方法來取得、設定及移除屬性,而非用屬性節點的方法來取得或改變屬性。

1
2
3
4
5
let headAttr = document.querySelector('.alert-heading').getAttributeNode('class') //獲得屬性節點
console.log(headAttr)//class="alert-heading"
console.log(typeof headAttr) //object
console.log(headAttr.nodeType)//2
console.log(headAttr.nodeValue) //alert-heading

上面介紹獲得屬性節點的方法,但其實還有設定(setAttributeNode())及移除(removeAttributeNode())屬性節點等等的方法,但在要操作屬性時,會傾向於用取得getAttribute('attrname')、設定setAttribute('attrname','attrvalue')、移除removeAttribute('attrname')等方法,但請注意,這些方法獲得或改變的並不是屬性節點,只是這些屬性的值而已。

1
2
3
4
let headAttr1 = document.querySelector('.alert-heading').getAttribute('class')
console.log(headAttr1)//alert-heading
console.log(headAttr1.nodeType)//undefined,因為不是節點,所以找不到
console.log(headAttr1.nodeValue) //undefined,因為不是節點,所以找不到

屬性的更多介紹及運用,可以參考MDN-Element

更多節點屬性及方法介紹:
MDN-Node
MDN-Attr
MDN-Node.nodeValue