真的很腦耶!
簡述
假設你有段 HTML 長這樣:
1 2 3 4 5 6 7 8
| <div class="block"> <p>lala la</p> <p>aaa</p> <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
|
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
|
function cleanJunkNodes (node) { for (let i=0; i<node.childNodes.length; i++) { let child = node.childNodes[i] if ( child.nodeType === 8 || child.nodeType === 3 && !/\S/.test(child.nodeValue) ) { node.removeChild(child) i-- } else if (child.nodeType === 1) { cleanJunkNodes(child) } } }
|
執行:
1 2 3
| cleanJunkNodes(document.body) cleanJunkNodes(document)
|
然後就可以快樂選元素了:
1
| const p = document.querySelector('.block').firstChild
|
備註一:
之所以可以這樣子做是因為傳入的節點是 Object
,而 Object
被傳到 function 當作參數的時候傳的是「參考」而不是「值」,所以你才能夠在 function 裡面改變外面 Object 的值。(不懂的話去複習 理解 function 傳遞參數的機制)
備註二:關於 nodeType
的代號
1
: element
9
: document -> 注意是 document 物件 不是 <html>
3
: text`
8
: comment
接下來你只要把上面的 function 跑一次就可以把所有不重要的元素清掉了(某種意義上很像 CSS reset?):
備註三:關於 cleanJunkNodes
的終止條件
當一個節點下面沒有子節點時,childNodes
會回傳一個「空陣列」,這時候就不會進入迴圈條件。