之前我們有提到,JavaScript 如何與瀏覽器溝通,於是我們討論到透過 JavaScript 取得 DOM API 節點,使瀏覽器可以處理使用者行為與互動。
DOM 是 W3C 制定的一個規範,是 HTML 與 XML 文檔的編程接口,一個 HTML 文件,我們可以用編輯器以代碼的形式展示,也可以用瀏覽器以頁面的形式展示,DOM 將文件檔解析為一個由節點和對象 ( 包含屬性和方法 ) 組成的結構集合,因此開發者能夠在一個規範及方法之下改變內容、結構及樣式。
我們可以透過 JavaScript 來獲取 DOM 的 document 和 window 節點 API,來操作裡面的文件以及訊息。而 DOM 的節點大致歸納為:元素節點、文字節點以及屬性節點。
以下為常見的 DOM 選取方法:
// 根據傳入的值,找到 DOM 中 id 為 'xxx' 的元素。
document.getElementById('xxx');
// 針對給定的 tag 名稱,回傳所有符合條件的 NodeList 物件
document.getElementsByTagName('xxx');
// 針對給定的 class 名稱,回傳所有符合條件的 NodeList 物件。
document.getElementsByClassName('xxx');
// 針對給定的 Selector 條件,回傳第一個 或 所有符合條件的 NodeList。
document.querySelector('xxx');
document.querySelectorAll('xxx');
Node 是一個接口,有許多接口都從 Node 繼承方法與屬性,下面為常用的 DOM 節點:
節點 | 數值 | 說明 |
---|---|---|
Node.ELEMENT_NODE |
1 | 一個元素節點,例如 <p> 和 <div> |
Node.TEXT_NODE |
3 | 實際文字節點,包括了換行與空格 |
Node.COMMENT_NODE |
8 | 一個 Comment 節點 |
Node.DOCUMENT_NODE |
9 | 一個 Document 節點 |
Node.DOCUMENT_TYPE_NODE |
10 | 描述文檔類型的 DocumentType 節點。例如 <!DOCTYPE html> 就是用於 HTML5 的 |
Node.DOCUMENT_FRAGMENT_NODE |
11 | 一個 DocumentFragment 節點 |
假如我們要判斷一個 Node 是否為一個元素,可以透過以上表格得知屬性值,並使用 nodeType
:
if(X.nodeType === 1){
console.log('X 是一個元素');
}
先前我們也有談論到「DOM Tree」,因此可以知道 DOM 是一層一層的,需要有效的抓取元素則需要弄清楚這一層一層的關係。
父子關係:
除了 document
之外,每一個節點都會有上層與下層的關係,上層稱為「父節點 ( Parent node ) 」,下曾則是「子節點 ( Child node ) 」。
兄弟關係:
故名思義就是同一個父親底下的產物(?),擁有同一個「父節點」的節點,他們的關係就是「兄弟節點 ( Siblings node ) 」。
父關係 API
parentNode
/ 每一個節點都會有一個 parentNode
屬性,它表示元素的父節點;我們可以透過 Node.parentNode 來取得父元素,回傳的值可能會是元素節點 ( Element )、根節點 ( Document ) 或 DocumentFragment 節點。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
console.log(li.parentNode.nodeName); // "ul"
</script>
parentElement
/ 回傳當前節點的父元素節點,如果該元素沒有父節點或者父節點不是一個 DOM 元素,則回傳 null。
子關係 API
childNodes
/ 回傳一個即時的 NodeList,表示元素的子節點列表,子節點可能會包含文本節點,註釋節點等,這邊我們可以透過 Node.hasChildNodes()
來檢查看看 DOM 節點是否有子節點:
var node = document.querySelector('#hi');
// 如果 node 內有子元素
if( node.hasChildNodes() ) {
// 可以透過 node.childNodes[n] (n 為數字索引) 取得對應的節點
// 注意,NodeList 物件內容為即時更新的集合
for (var i = 0; i < node.childNodes[i].length; i++) {
// ...
};
}
Node.childNodes
可能會回傳的有:
firstChild
/ 取得 node
第一個子節點,如果沒有子節點,則回傳 null
。
底下可以看到子節點也會抓取空白行,因此這裡抓到的會是 ul
與 li
中間的空白行,所以回傳 undefined
。
<ul>
<li>text 1</li>
<li>text 2</li>
<li>text 3</li>
</ul>
<script>
var ul = document.querySelector('ul');
// tagName 屬性可以取得 node 的標籤名稱
console.log(ul.firstChild.tagName); // undefined
</script>
如果我們改成這樣:
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var ul = document.querySelector('ul');
console.log(ul.firstChild.tagName); // LI
</script>
少了空白行就可以正確抓取我們要的第一個子節點。
lastChild
/ 取得 node
最後一個子節點,如果沒有子節點,則回傳 null。
<ul>
<li>text 1</li>
<li>text 2</li>
<li>text 3</li>
</ul>
<script>
var ul = document.querySelector('ul');
// textContent 屬性可以取得節點內的文字內容
console.log(ul.lastChild.textContent); // '\n\t' (換行字元)
</script>
這邊跟 firstChild
****的情況一樣,所以我們同樣改成底下範例,把空行去掉:
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var ul = document.querySelector('ul');
console.log(ul.lastChild.textContent); // 'text 3'
</script>
也成功抓取到我們要的最後一個子節點。
hasChildNodes
/ 這個方法會回傳一個布林值,用途在上面有說到,他替我們抓取當前是否含有子節點。
底下給一個範例,如果 id 為 a 的這個元素有子節點,則從 DOM 中刪除第一個子節點:
var a = document.getElementById("a");
if ( foo.hasChildNodes() ) {
foo.removeChild(a.childNodes[0]);
}
兄弟關係 API
previousSibling
/ 回傳當前節點的前一個兄弟節點,沒有則回傳 null
。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
// 這裡已經沒有前一個兄弟節點
console.log(li.previousSibling); // null
// document.querySelectorAll 會取得所有條件符合的元素
// document.querySelectorAll('span')[2] 指的是「第三個」符合條件元素
var li2 = document.querySelectorAll('li')[2];
console.log(li2.previousSibling.textContent); // 'text 2'
</script>
nextSibling
/ 回傳當前節點的下一個兄弟節點,沒有則回傳 null
。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
// 這裡直接抓取 text 1 的下一個兄弟節點
console.log(li.nextSibling.textContent); // 'text 2'
</script>
參考資料:
重新認識 JavaScript: Day 12 透過 DOM API 查找節點
JavaScript操作DOM常用的API