DOM 清掉惱人的文字節點

真的很腦耶!

簡述

假設你有段 HTML 長這樣:

1
2
3
4
5
6
7
8
<div class="block">
<!-- yo -->
<p>lala la</p>
<!-- yo -->
<p>aaa</p>
<!-- yo -->
<p>bbbb</p>
</div>

那生成 DOM 的時候會變這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DIV
#text ("\n\t") -> 文字節點(換行)
#comment -> 註解節點
#text ("\n\t") -> 文字節點(換行)
+ P
| + #text ("lala la") -> 文字節點(p 的內容)
+ #text ("\n\t") -> 文字節點(換行)
#comment -> 註解節點
#text ("\n\t") -> 文字節點(換行)
+ P
| + #text ("aaa") -> 文字節點(p 的內容)
+ #text ("\n\t") -> 文字節點(換行)
#comment -> 註解節點
#text ("\n\t") -> 文字節點(換行)
+ P
| + #text ("bbbb") -> 文字節點(p 的內容)
+ #text ("\n\t") -> 文字節點(換行)

所以這時候如果想抓到 .block 下的第一個 <p>,你可能會這樣寫:

1
const p = document.querySelector('.block').firstChild

但這樣會抓到的是 #text ("\n\t") 這個文字節點(空白字元),所以要這樣子才能真的找到 <p>

1
2
3
4
5
6
7
8
9
10
11
/*
firstChild: #text ("\n\t")
nextSibling: #comment
nextSibling: #text ("\n\t")
nextSibling: <p> !!!
*/
const p = document.querySelector('.block')
.firstChild
.nextSibling
.nextSibling
.nextSibling

真的是有夠麻煩…,所以繼續往下看該怎麼解決。

通通燒毀!

所以這邊寫一個 function 來把這些沒用的節點一次處理掉:

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
28
29
30
/*
cleanJunkNodes 會接收一個節點:
1. 取得該節點下的所有子節點
2. 判斷子節點的值
3. 如果是沒用的節點就刪掉
*/
function cleanJunkNodes (node) {
// 遍歷所有子節點
for (let i=0; i<node.childNodes.length; i++) {
// 存取第 i 個子節點
let child = node.childNodes[i]
// 如果是註解 or 文字節點(只有空白字元)
if
(
child.nodeType === 8
||
child.nodeType === 3 && !/\S/.test(child.nodeValue)
)
{
// 刪除子節點
node.removeChild(child)
// 往前退一格(因為長度變短了)
i--
}
else if (child.nodeType === 1) {
// 如果子節點也是元素,丟到遞迴 cleanJunkNodes 清除垃圾節點
cleanJunkNodes(child)
}
}
}

執行:

1
2
3
// (以下兩個都可以)
cleanJunkNodes(document.body)
cleanJunkNodes(document)

然後就可以快樂選元素了:

1
const p = document.querySelector('.block').firstChild // 真的是 <p>

備註一:

之所以可以這樣子做是因為傳入的節點是 Object,而 Object 被傳到 function 當作參數的時候傳的是「參考」而不是「值」,所以你才能夠在 function 裡面改變外面 Object 的值。(不懂的話去複習 理解 function 傳遞參數的機制

備註二:關於 nodeType 的代號

  • 1: element
  • 9: document -> 注意是 document 物件 不是 <html>
  • 3: text`
  • 8: comment

接下來你只要把上面的 function 跑一次就可以把所有不重要的元素清掉了(某種意義上很像 CSS reset?):

備註三:關於 cleanJunkNodes 的終止條件

當一個節點下面沒有子節點時,childNodes 會回傳一個「空陣列」,這時候就不會進入迴圈條件。

實作 DOM 的 closest 方法 mentor-program-day39
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×