JavaScript DOM 查找元素


DOM (Document Object Model) 定義了一組標準 API (Application Programming Interface) 讓我們可以用 JavaScript 對 HTML 文件做操作。

DOM 將一份 HTML 文件看作是一個樹狀結構的物件,讓我們可以方便直觀的存取樹中的節點 (node) 來改變其結構、樣式 (CSS) 或內容等。

document 物件是 DOM tree 的根節點,表示整份 HTML 文件,通常你要存取 HTML 都是從 document 物件開始:



圖片來源: Wikipedia DOM


HTML DOM 規範中定義了這些類型的 API:

  • 讓我們可以對 HTML 的元素 (element) 當作是 JavaScript 物件 (object) 來操作
  • 定義了 HTML 元素有哪些屬性 (properties) 可以來做存取
  • 定義了 HTML 元素有哪些方法 (methods) 可以來被操作
  • 定義了 HTML 元素事件 (events),讓我們可以針對特定元素來綁定事件處理函式 (例如使用者按下滑鼠、在鍵盤打字都是所謂的事件)

document.getElementById(id)

透過 id 取得一個 HTML 元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<body>

<p id="demo">Click the button to change the text in this paragraph.</p>

<button onclick="myFunction()">Try it</button>

<script>
function myFunction() {
document.getElementById("demo").innerHTML = "Hello World";
}
</script>

</body>
</html>

document.getElementsByTagName(name)

用來根據 HTML 標籤 (tag) 名稱取得所有這個標籤的元素集合 (HTMLCollection),返回的結果是一個像陣列 (array) 的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<body>

<p>An unordered list:</p>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>

<p>Click the button to display the innerHTML of the second li element (index 1).</p>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
var x = document.getElementsByTagName("LI");
document.getElementById("demo").innerHTML = x[1].innerHTML;

}
</script>

</body>
</html>

document.getElementsByName(name)

用來取得特定名稱 (name) 的 HTML 元素集合 (HTMLCollection),返回的結果是一個像陣列 (array) 的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<body>

First Name: <input name="fname" type="text" value="Michael"><br>
First Name: <input name="fname" type="text" value="Doug">

<p>Click the button to get the tag name of the first element in the document that has a name attribute with the value "fname".</p>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
var x = document.getElementsByName("fname")[0].tagName;
document.getElementById("demo").innerHTML = x;

}
</script>

</body>
</html>

document.getElementsByClassName(names)

用來取得特定類別名稱 (class name) 的 HTML 元素集合 (HTMLCollection),返回的結果是一個像陣列 (array) 的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<body>

<div class="example">First div element with class="example".</div>

<div class="example">Second div element with class="example".</div>

<p>Click the button to change the text of the first div element with class="example" (index 0).</p>

<button onclick="myFunction()">Try it</button>

<p><strong>Note:</strong> The getElementsByClassName() method is not supported in Internet Explorer 8 and earlier versions.</p>

<script>
function myFunction() {
var x = document.getElementsByClassName("example");
x[0].innerHTML = "Hello World!";
}
</script>

</body>
</html>

可以使用空白隔開多個 class name,元素必須有所有指定的 class name 才符合。例如:

1
2
3
// 取得同時有 demo 和 test 兩個 class name 的所有元素

document.getElementsByClassName('demo test');

document.querySelector(selectors)

使用 CSS 選擇器 (CSS selectors) 來尋找符合條件且第一個找到的 HTML 元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<body>

<h2 class="example">A heading with class="example"</h2>
<p class="example">A paragraph with class="example".</p>

<p>Click the button to add a background color to the first element in the document with class="example".</p>

<button onclick="myFunction()">Try it</button>

<script>
function myFunction() {
document.querySelector(".example").style.backgroundColor = "red";
}
</script>

</body>
</html>

document.querySelectorAll(selectors)

使用 CSS 選擇器 (CSS selectors) 來尋找所有符合條件的 HTML 元素集合 (NodeList)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<body>

<h2 class="example">A heading with class="example"</h2>
<p class="example">A paragraph with class="example".</p>

<p>Click the button to add a background color all elements with class="example".</p>

<button onclick="myFunction()">Try it</button>

<p><strong>Note:</strong> The querySelectorAll() method is not supported in Internet Explorer 8 and earlier versions.</p>

<script>
function myFunction() {
var x, i;
x = document.querySelectorAll(".example");
for (i = 0; i < x.length; i++) {
x[i].style.backgroundColor = "red";
}
}
</script>

</body>
</html>

DOM tree 節點間位置的相互關係

由於 DOM 節點有分層的概念,於是節點與節點之間的關係,我們大致上可以分成兩種:

  • 父子關係
    除了 document 之外,每一個節點都會有個上層的節點,我們通常稱之為「父節點」 (Parent node),而相對地,從屬於自己下層的節點,就會稱為「子節點」(Child node)。

  • 兄弟關係:有同一個「父節點」的節點,那麼他們彼此之間就是「兄弟節點」(Siblings node)。



圖片來源:重新認識 JavaScript: Day 12 透過 DOM API 查找節點 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

Node.childNodes

所有的 DOM 節點物件都有 childNodes 屬性 (read-only property),可以用來取得該元素下的所有子元素集合 (NodeList)。

1
2
3
4
5
6
7
8
9
10
11
12
13
var demo = document.querySelector('#demo');


// 如果 node 內有子元素
if( demo.hasChildNodes() ) {

// 可以透過 demo.childNodes[n] (n 為數字索引) 取得對應的節點

// 注意,NodeList 物件內容為即時更新的集合
for (var i = 0; i < demo.childNodes[i].length; i++) {
// ...
};
}

Node.childNodes 回傳的可能會有這幾種:

  • HTML 元素節點 (element nodes)
  • 文字節點 (text nodes),包含空白
  • 註解節點 (comment nodes)

Node.children

DOM 節點物件的 children 屬性和 childNodes 屬性類似,差異在於 childNodes 返回的子元素會包含文字節點 (text nodes) 和註解節點 (comment nodes),children 屬性則只會返回 HTML 元素節點 (HTMLCollection)。

Node.firstChild

Node.firstChild 可以取得 Node 節點的第一個子節點,如果沒有子節點則回傳 null

要注意的是,子節點包括「空白」節點,所以像下面範例:

1
2
3
4
5
6
7
8
9
10
11
12
<p>
<span>span 1</span>
<span>span 2</span>
<span>span 3</span>
</p>

<script>
var p = document.querySelector('p');

// tagName 屬性可以取得 node 的標籤名稱
console.log(p.firstChild.tagName); // undefined
</script>

因為拿到的是 <p> 與第一個 <span> 中間的「換行字元」,所以 p.firstChild.tagName 會得到 undefined

另一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
<p id="demo">

<span>First span</span>
</p>

<script>
var p = document.getElementById('demo');

// 會顯示 "#text",因為第一個子元素是換行字元

alert(p.firstChild.nodeName);
</script>

可以把第一個例子修改成以下:

1
2
3
4
5
6
7
8
<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script>
var p = document.querySelector('p');

// tagName 屬性可以取得 node 的標籤名稱
console.log(p.firstChild.tagName); // "SPAN"
</script>

把中間的換行與空白移除,就會得到預期中的 "SPAN" 了。

第二個例子修改方式也一樣:

1
2
3
4
5
6
7
8
9
<p id="demo"><span>First span</span></p>


<script>
var p = document.getElementById('demo');

// 會顯示 "SPAN"
alert(p.firstChild.nodeName);
</script>

Node.lastChild

Node.lastChild 可以取得 Node 節點的最後一個子節點,如果沒有子節點則回傳 null

Node.firstChild 一樣的是,子節點包括「空白」節點:

1
2
3
4
5
6
7
8
9
<p id="demo"><span>First span</span><span>Second span</span><span>Last span</span></p>


<script>
var p = document.getElementById('demo');

// 會顯示 "Last span"
alert(p.lastChild.innerHTML);
</script>

Node.parentNode

透過 Node.parentNode 可以用來取得父元素,回傳值可能會是一個元素節點 (Element node)、根節點 (Document node) 或 DocumentFragment 節點。

1
2
3
4
5
6
7
8
9
10
11
<p>
<span id="demo">my span</span>
</p>


<script>
var elem = document.getElementById('demo');

// 會顯示 "P"
alert(elem.parentNode.nodeName);
</script>

Node.previousSibling

透過 Node.previousSibling 可以取得同層之間的「前一個」節點,如果 node 已經是第一個節點,則回傳 null

1
2
3
4
5
6
7
8
9
<div><span id="s1">s1</span><span id="s2">s2</span></div>

<script>
// 會顯示 null
alert(document.getElementById('s1').previousSibling);

// 會顯示 "s1"
alert(document.getElementById('s2').previousSibling.id);
</script>

第二個例子:

1
2
3
4
5
6
7
8
9
10
11
<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script>
var el = document.querySelector('span');
console.log( el.previousSibling ); // null

// document.querySelectorAll 會取得所有符合條件的集合,
// 而 document.querySelectorAll('span')[2] 指的是「第三個」符合條件的元素。
var el2 = document.querySelectorAll('span')[2];
console.log( el2.previousSibling.textContent ); // "span 2"
</script>

Node.nextSibling

透過 Node.previousSibling 可以取得同層之間的「下一個」節點,如果 node 已經是最後一個節點,則回傳 null

1
2
3
4
5
6
7
8
9
<div><span id="s1">s1</span><span id="s2">s2</span></div>

<script>
// 會顯示 "s2"
alert(document.getElementById('s1').nextSibling.id);

// 會顯示 null
alert(document.getElementById('s2').nextSibling);
</script>

第二個例子:

1
2
3
4
5
6
7
8
<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script>
    // document.querySelector 會取得第一個符合條件的元素
    var el = document.querySelector('span');

    console.log( el.nextSibling.textContent ); // "span 2"
</script>

上面介紹的很多 DOM 查找方式會返回一個元素集合,是一個像陣列的物件 - 有 length 屬性、可以用 for 迴圈遍歷結果,雖然不能使用陣列型別的 method,但這兩種都可以用「陣列索引」的方式來存取內容。

而 NodeList 和 HTMLCollection 的差別在於,NodeList 包含任何的節點類型,除了 HTML element 節點,也包含文字節點、屬性節點等。HTMLCollection 則只包含 HTML 元素節點 (Element nodes)

像是 document.getElementsBy** (注意,有個 s) 以及 document.querySelectorAll 分別回傳 「HTMLCollection」 與 「NodeList」。

另一個需要注意的地方是,HTMLCollection / NodeList 在大部分情況下是即時更新的,但透過 document.querySelectorAll 會回傳一個靜態的 NodeList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="outer">
<div id="inner">inner</div>
</div>

<script>

// <div id="outer">
var outerDiv = document.getElementById('outer');

// 所有的 <div> 標籤
var allDivs = document.getElementsByTagName('div');

console.log(allDivs.length); // 2

// 清空 <div id="outer"> 下的節點
outerDiv.innerHTML = '';

// 因為清空了<div id="outer"> 下的節點,所以只剩下 outer
console.log(allDivs.length); // 1
</script>

如果改成 document.querySelector 的寫法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="outer">
<div id="inner">inner</div>
</div>

<script>

// <div id="outer">
var outerDiv = document.getElementById('outer');

// 所有的 <div> 標籤
var allDivs = document.querySelectorAll('div');

console.log(allDivs.length); // 2

// 清空 <div id="outer"> 下的節點
outerDiv.innerHTML = '';

// document.querySelector 回傳的是靜態的 NodeList,不受 outerDiv 更新影響
console.log(allDivs.length); // 2
</script>

參考文獻

  1. https://ithelp.ithome.com.tw/articles/10191765

  2. https://www.fooish.com/javascript/dom/

  3. https://www.fooish.com/javascript/dom/traversing.html