DOM 允許我們對元素及其內容執行任何操作,但我們首先需要找到對應的 DOM 物件。
所有 DOM 上的操作都從 document
物件開始。那是 DOM 的主要「進入點」。我們可以從中存取任何節點。
以下是允許在 DOM 節點之間移動的連結圖片
讓我們更詳細地討論它們。
最上方:documentElement和主體
最上層的樹狀節點可直接用 document
屬性取得
<html>
=document.documentElement
- 最上層的文件節點為
document.documentElement
。這是<html>
標籤的 DOM 節點。 <body>
=document.body
- 另一個廣泛使用的 DOM 節點是
<body>
元素 –document.body
。 <head>
=document.head
<head>
標籤可用於document.head
。
document.body
可能為 null
腳本無法存取在執行時不存在的元素。
特別是,如果腳本在 <head>
內部,則 document.body
不可使用,因為瀏覽器尚未讀取它。
因此,在以下範例中,第一個 alert
會顯示 null
<html>
<head>
<script>
alert( "From HEAD: " + document.body ); // null, there's no <body> yet
</script>
</head>
<body>
<script>
alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists
</script>
</body>
</html>
null
表示「不存在」在 DOM 中,null
值表示「不存在」或「沒有此節點」。
子節點:childNodes、firstChild、lastChild
從現在開始,我們將使用兩個術語
- 子節點(或子節點) – 直接的子元素。換句話說,它們正好巢狀在給定的元素中。例如,
<head>
和<body>
是<html>
元素的子節點。 - 後代 – 巢狀在給定元素中的所有元素,包括子節點、其子節點,依此類推。
例如,這裡 <body>
有子節點 <div>
和 <ul>
(以及一些空白文字節點)
<html>
<body>
<div>Begin</div>
<ul>
<li>
<b>Information</b>
</li>
</ul>
</body>
</html>
…而 <body>
的後代不僅是直接子節點 <div>
、<ul>
,還包括更深層巢狀的元素,例如 <li>
(<ul>
的子節點)和 <b>
(<li>
的子節點)– 整個子樹。
childNodes
集合列出所有子節點,包括文字節點。
以下範例顯示 document.body
的子節點
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
</script>
...more stuff...
</body>
</html>
請注意這裡一個有趣的細節。如果我們執行上述範例,顯示的最後一個元素是 <script>
。事實上,文件下方還有更多內容,但在執行腳本時,瀏覽器尚未讀取它,因此腳本看不到它。
屬性 firstChild
和 lastChild
提供快速存取第一個和最後一個子節點。
它們只是簡寫。如果存在子節點,則以下內容始終為真
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
還有一個特殊函數 elem.hasChildNodes()
用於檢查是否有任何子節點。
DOM 集合
正如我們所見,childNodes
看起來像一個陣列。但實際上它不是陣列,而是一個集合 - 一個特殊的類陣列可迭代物件。
這有兩個重要的後果
- 我們可以使用
for..of
來迭代它
for (let node of document.body.childNodes) {
alert(node); // shows all nodes from the collection
}
這是因為它是可迭代的(提供 Symbol.iterator
屬性,如要求)。
- 陣列方法不起作用,因為它不是陣列
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
第一件事很好。第二件事是可以容忍的,因為如果我們想要陣列方法,我們可以使用 Array.from
從集合中建立一個「真正的」陣列
alert( Array.from(document.body.childNodes).filter ); // function
DOM 集合,甚至更多 - 本章中列出的所有導覽屬性都是唯讀的。
我們不能透過指定 childNodes[i] = ...
來用其他東西取代子節點。
變更 DOM 需要其他方法。我們將在下一章看到它們。
幾乎所有 DOM 集合,除了少數例外,都是動態的。換句話說,它們反映了 DOM 的當前狀態。
如果我們保留對 elem.childNodes
的參照,並在 DOM 中新增/移除節點,則它們會自動出現在集合中。
for..in
來迴圈集合集合使用 for..of
可迭代。有時人們會嘗試使用 for..in
來執行此操作。
請不要這樣做。for..in
迴圈會迭代所有可列舉的屬性。而集合有一些我們通常不想要的「額外」很少使用的屬性
<body>
<script>
// shows 0, 1, length, item, values and more.
for (let prop in document.body.childNodes) alert(prop);
</script>
</body>
兄弟節點和父節點
兄弟節點 是同一個父節點的子節點。
例如,這裡的 <head>
和 <body>
是兄弟節點
<html>
<head>...</head><body>...</body>
</html>
<body>
被稱為<head>
的「下一個」或「右邊」兄弟節點,<head>
被稱為<body>
的「上一個」或「左邊」兄弟節點。
下一個兄弟節點在 nextSibling
屬性中,上一個兄弟節點在 previousSibling
中。
父節點可用作 parentNode
。
例如
// parent of <body> is <html>
alert( document.body.parentNode === document.documentElement ); // true
// after <head> goes <body>
alert( document.head.nextSibling ); // HTMLBodyElement
// before <body> goes <head>
alert( document.body.previousSibling ); // HTMLHeadElement
僅元素導覽
上面列出的導覽屬性是指所有節點。例如,在 childNodes
中,我們可以看到文字節點、元素節點,甚至如果它們存在的話,還可以看見註解節點。
但對於許多任務,我們不需要文字或註解節點。我們想要操作表示標籤並形成頁面結構的元素節點。
因此,讓我們查看更多僅考慮元素節點的導覽連結
這些連結類似於上面提供的連結,只是裡面有 Element
字詞
children
– 僅為元素節點的那些子節點。firstElementChild
、lastElementChild
– 第一個和最後一個元素子節點。previousElementSibling
、nextElementSibling
– 鄰近元素。parentElement
– 父元素。
parentElement
?父節點不能是元素嗎?parentElement
屬性傳回「元素」父節點,而 parentNode
傳回「任何節點」父節點。這些屬性通常相同:它們都取得父節點。
除了 document.documentElement
之外
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
原因是根節點 document.documentElement
(<html>
) 的父節點為 document
。但 document
不是元素節點,因此 parentNode
會傳回它,而 parentElement
則不會。
當我們想要從任意元素 elem
往上升到 <html>
,但不升到 document
時,這個細節可能很有用
while(elem = elem.parentElement) { // go up till <html>
alert( elem );
}
讓我們修改上面的一個範例:將 childNodes
替換為 children
。現在它只顯示元素
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
for (let elem of document.body.children) {
alert(elem); // DIV, UL, DIV, SCRIPT
}
</script>
...
</body>
</html>
更多連結:表格
到目前為止,我們描述了基本的導覽屬性。
某些類型的 DOM 元素可能會提供額外的屬性,這些屬性特定於它們的類型,以方便使用。
表格就是一個很好的範例,而且是一個特別重要的案例
<table>
元素支援(除了上面提供的屬性之外)這些屬性
table.rows
– 表格中<tr>
元素的集合。table.caption/tHead/tFoot
– 參照元素<caption>
、<thead>
、<tfoot>
。table.tBodies
–<tbody>
元素的集合(根據標準可以有很多個,但至少會有一個 - 即使原始 HTML 中沒有,瀏覽器也會將它放入 DOM 中)。
<thead>
、<tfoot>
、<tbody>
元素提供 rows
屬性
tbody.rows
– 內部的<tr>
集合。
<tr>
:
tr.cells
–<tr>
內的<td>
和<th>
儲存格的集合。tr.sectionRowIndex
– 給定的<tr>
在封閉的<thead>/<tbody>/<tfoot>
內的位置(索引)。tr.rowIndex
–<tr>
在整個表格中的編號(包括所有表格列)。
<td>
和 <th>
td.cellIndex
– 儲存格在封閉的<tr>
內的編號。
使用範例
<table id="table">
<tr>
<td>one</td><td>two</td>
</tr>
<tr>
<td>three</td><td>four</td>
</tr>
</table>
<script>
// get td with "two" (first row, second column)
let td = table.rows[0].cells[1];
td.style.backgroundColor = "red"; // highlight it
</script>
規格:表格資料。
HTML 表單也有額外的導覽屬性。我們在開始使用表單時會再探討。
摘要
給定一個 DOM 節點,我們可以使用導覽屬性前往它的鄰近節點。
導覽屬性主要有兩組
- 所有節點:
parentNode
、childNodes
、firstChild
、lastChild
、previousSibling
、nextSibling
。 - 僅限元素節點:
parentElement
、children
、firstElementChild
、lastElementChild
、previousElementSibling
、nextElementSibling
。
某些類型的 DOM 元素(例如表格)提供額外的屬性和集合來存取其內容。
留言
<code>
標籤,要插入多行程式碼,請將它們包覆在<pre>
標籤中,要插入超過 10 行程式碼,請使用沙盒 (plnkr、jsbin、codepen…)