深入了解 jQuery 選取器 實作的原理
前言
在 Youtube 直播看到 Alex 大大,深入說明 jQuery 底層的程式碼,在此紀錄 選取器 的脈絡。
Node.nodeType
在 jQuery 的原始碼裡,很常使用 Node.nodeType
來判斷取得物件的種類,
所以我們要先了解 Node.nodeType
是什麼。
常出現的號碼,請記
- 1
- 表示元素的 Element 節點,如
<body>
、<a>
、<p>
、<script>
、<style>
、<html>
、<h1>
或<div>
。
- 表示元素的 Element 節點,如
- 3
- 表示 HTML 元素屬性的 Attr 節點 或 實際文字字元的 Text 節點,它包括了「換行與空格」。
- 9
- 表示文件的 Document 節點。
Node.nextSibling
有時候我們會需要取得 目前元素<div>
的下一個兄弟元素<div>
,這時我們會使用 element.nextSibling
來完成這個任務,偏偏有時回傳給我們的東西,竟然不是我們的好兄弟元素<div>
,而是一個奇怪的東西,這奇怪的東西正好是上述所說的 Node.nodeType 為 3
。
範例測試
1 | <div id="div-01">Here is div-01</div> |
1 | var el = document.getElementById('div-01').nextSibling, |
由上例可看到 div-01
取下一個元素時,回傳的是 #text
,而不是 DIV
元素。
解決#text 問題
那我們如何解決這個問題呢?下列有幾種解法供參考。
解法 1
使用 nodeType
判斷,當我們取得下一個的話,判斷 el.nextSibling !== 1
時,就代表不是元素的 Element
節點,所以需要再往下抓下一個,直到 el.nextSibling === 1
時,才是我們要的 div
元素。
解法 2
使用 element.nextElementSibling
取得下一個元素,它幫會我們避開 el.nextSibling !== 1
的東西,若你看他的原始碼的話,他也是使用 解法 1
的寫法去執行,只是此函式事先幫我們判斷好而已。
for loop 潮的寫法
以往我們看到使用 for loop
的寫法如下
1 | for (let i = 0; i < 5; i++) { |
再開始介紹 for loop
潮的寫法之前,這裡先重新複習 for loop
的語法結構
英文版:for ([initialization]; [condition]; [final-expression])
中文版:for ([宣告]; [條件]; [改變])
我就用中文來解說
宣告
- 就等於我們一般在使用的
let a = 10
、let b = 87
- 就等於我們一般在使用的
條件
- 就像我們使用
if
的判斷式一樣,if(a === 9)
- 就像我們使用
看完說明後,接下來我們來看 jQuery 裡,使用比較潮的寫法
1 | var siblings = function(n, elem) { |
注意上例 for loop
的寫法,他第一個 initialization
竟然沒有寫,為什麼呢?
下面我們用簡單的範例來說明
1 | var sibling = elem.parentNode.firstChild; |
其實你注意 for loop
的上面一行,
可以看到它只是提前把 for loop
裡第一個 initialization
拉出來外層撰寫,所以當開始執行 for loop
時,就不必再宣告一次,程式就可以直接執行了。
jQuery 的 siblings
再來我們來看 jQuery 的 siblings 函式時,紀錄二個重點
重點 1
Q.siblings
如何實現取得目前元素之相鄰的兄弟元素?
A.
最初的想法,我們可以使用 Node.nextSibling
和 Node.previousSibling
,分別 for loop
往前往後各跑一次抓取。
不過我們來看一下 jQuery
,是如何實現呢?
1 | var siblings = function(n, elem) { |
1 | siblings: function( elem ) { |
主要的重點在於 呼叫siblings
裡的 ( elem.parentNode || {} ).firstChild
,它的作法為
- 由目前元素 先往上一層父元素後,再往下層抓排序第一個的子元素,這樣就回到兄弟元素的第一個了。
- 接著開始從第一個元素,
for loop
使用nextSibling
取得下一個元素,並且過程中排除自己。
這樣的話,就可以取得所有相鄰的兄弟元素。
重點 2
延續上個案例
Q.(elem.parentNode || {}).firstChild
之裡面的 {}
是做什麼用的?
A.
其實後面的 {}
是用來擋 程式錯誤 噴error
的,
如果當elem
是沒東西時,這時就會給 {}
值,而當 JS 執行此段語法 {}.firstchild
時,
會回傳 undefined
值,至少 js 還看得懂 undefined
,所以就不會出現 error訊息
,造成網站掛了,但若是直接用 undefined.firstchild,就會死給你看。
範例說明
可以將下列的程式碼,貼到 Chrome 開發者工具試試。
1 | let a; // 沒給值,預設為 undefined |
1 | let b = {}; |
情境說明
以 AJAX
為例
res.data.member[0].name
有時跟後端串 API 後,回傳的資料要取得 User 的名字,
但有時避免後端開錯規格或遺漏掉的話,避免 JS 執行時,直接噴錯誤的話,
我們就要自行新增一些 擋煞 的機制,參考寫法如下
1 | if (res.data) { |