深入了解 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) { |
