Sizzle源碼分析:一 設計思路

一.前言正則表達式

DOM選擇器(Sizzle)是jQuery框架中很是重要的一部分,在H5尚未流行起來的時候,jQuery爲咱們提供了一個簡潔,方便,高效的DOM操做模式,成爲那個時代的經典。雖然如今Vue,React等MVVM框架的熱度如日中天,可是瞭解下jQuery的DOM選擇器設計思路,能夠學習到Sizzle設計的精妙之處,爲本身模塊設計和框架設計提供很好的參考意義,也爲了解MVVM框架虛擬DOM打下更好的基礎。數組

 

二.Sizzle的特別之處瀏覽器

首先介紹下jQuery選擇器模塊,就是Sizzle選擇器,他的網址是http://sizzlejs.com/,若是你只須要進行文檔節點的查詢,能夠直接引入Sizzle的文件就能夠了,而不須要整個jQuery文件。
Sizzle選擇器有哪些特色呢?數據結構

1. 高效,Sizzle經過不少方法來實現了極致的訪問速度,爲咱們搜索DOM節點提供了一個很好的指導,號稱是當時最快的DOM選擇器引擎。app

2. 支持多種查詢方式,包括基本選擇器(ID,Class,TAG),層級選擇器,僞類選擇器等等,符合多種複雜場景。框架

3. 體積小,壓縮後只有3Kdom

 

Sizzle快具體在哪些緣由呢,主要從幾個角度來分析函數

1. 優先瀏覽器本地API:好比基本選擇器最終調用的是getElementById等等,對於複雜選擇器若是支持querySelector接口,優先使用querySelector來查詢。最後對比較老舊的選擇器才使用本身的查詢邏輯。那使用瀏覽器本地API比JS本地執行性能高出不少,不在一個數量級。性能

2. 優化選擇符:經過兩個角度來優化,一是儘可能縮小DOM根節點,縮小搜索的範圍,另外是尋找備選種子集合,經過本地接口過濾出備選種子集合,而不是去搜索全部的DOM節點學習

3. 經過從右向左的方式來解析,在大多數狀況下效率高出從左向右的模式不少

4. 經過建立編譯函數,經過空間換時間的方式,來提升相同選擇符的查詢性能,每一個選擇符查詢以後都會被詞法分析,而後建立爲過濾函數,只要對種子集合執行過濾函數便可。

 

在介紹Sizzle源碼以前,先解釋一下從右向左分析的思路,好比有個選擇符#div[name=wrapper] div[name=ad2] 若是是咱們來分析這個字符串應該怎麼分析?咱們有兩個選擇

從左到右分析 和 從右到左分析,那麼哪一個方案更優呢?答案是從右向左,即便是瀏覽器渲染CSS也一般是這個規則,爲啥呢?

咱們考慮下HTML的基本結構,HTML被瀏覽器首先解析爲DOM樹相似於下面的結構:

 

假如咱們要查詢ad2這個div,$("#div[name=wrapper] div[name=ad2]")

1. 按從左往右的思路,咱們首先要找到全部的Div,而後對每一個Div是否是warpper,找到之後再對比他的子節點,看看他是否是ad2,對於一個嵌套很深的DOM樹來講,每一個Div可能存在不少子節點,那麼每次遍歷子節點的過程將會很是耗時,這是由於父與子的關係是一對多的關係。

2. 按從右向左的思路,咱們首先找到全部的DIV,而後看看這個DIV是否是ad2,若是是的話再往上一層父節點查看,是否是wrapper,由於每一個節點只有一個父節點,那麼這個查詢過程瞬間訊速了不少,是否是,由於子於父的關係是多對一,咱們知道了子,那就等因而1對1,因此這個過程查詢的機率效率確定要比從左向右迅速許多。

 

三.如何分析框架源碼

Sizzle.js的源碼總共有2000多行,裏面包含了不少的正則表達式,函數和兼容性處理,咋一看頭都是懵的,這裏我以爲讀框架的源碼須要有兩個思路:

1. 簡化模塊,把主線留下:

  首先把源碼分層,好比jQuery的事件和委託機制,以前文章中介紹過,總共分了4,5層,這樣一層一層的分析,能夠由底向上,集中注意力,一點點解開源碼的大門,不然各類模塊耦合在一塊兒會讓你看的懷疑人生。

2. 理清思路,找出設計圖紙

  瞭解做者的思路,咱們每一個人在編碼的時候是有一個設計流程或者設計圖,還有數據結構,咱們首先就要經過註釋或者相關資料瞭解做者的這些思路,能夠很快的讀通源碼流程,而不是一上來就淹沒在源碼中,效率很低。

 

四.Sizzle框架設計思路分析

首先那咱們在分析Sizzle的時候就首先作一個分層處理:

第一層 把兼容性相關邏輯去掉,只保留最多見的選擇符的流程,咱們假設咱們的瀏覽器都是沒有bug的,只須要走正常流程。

第二層 咱們把比較複雜的位置僞類相關的邏輯去掉,只考慮普通選擇符和層級選擇符,好比 $("#div_test > span input[checked=true]"),先不考慮相似:first等位置僞類,這樣,源碼一會兒就精簡了不少,等分析完了再加上去掉的邏輯。

第二,咱們須要把Sizzle查詢的總體思路給畫出來,把做者的設計思路畫出來,再分析源碼就清晰不少。

  而後咱們來了解一下Sizzle的整個流程圖:

首先瀏覽器先作兼容性和初始化的一些處理,這些略過,而後經過正則表達式判斷當前的選擇符是否是 ID或者Class或者Tag的簡單表達式,若是是的話直接調用JS原生接口getElementById/getElementsByClassName/getElementsByTagName來查詢結果,這種效率是最高的,由於JS原生API是性能最好的。

若是是複雜選擇器,好比帶層級關係或者帶僞類等,再判斷瀏覽器是否是支持querySelectorAll高級查詢,若是支持,調用querySelectorAll便可,這也是性能比較高的方案,可是若是咱們的瀏覽器版本比較低不支持的話,就只能走下面Sizzle本身的方式來了。

因而可知,隨着ES標準的發展,jQuery也引入了最新的API,從而實現了性能的最大優化。

 

 如今進入到Sizzle本身的邏輯來了,首先進入select函數,看看整個流程,好比對於#div_test > span input[checked=true]

1. 首先進入詞法分析過程tokenize把選擇符字符串轉換爲token數組,以便後面分析使用,具體過程咱們後面再說

2. 嘗試縮小上下文範圍,默認上下文是document,在這裏咱們發現#div_test是個ID選擇符,能夠直接把上下文定位到div_test這個節點,從而提升了查詢性能

3. 嘗試尋找一個初始集合seed,也就是說縮小備選dom列表,這裏是input,因此咱們把div_test節點下的全部子節點中的input節點做爲seed數組保存起來

4. 將剩下的選擇符進行編譯保存,而後執行編譯函數獲得結果。

這裏有幾個細節說明一下,tokenize函數實現的過程是不少編譯器實現的一種方式,好比js代碼在執行以前也是從字符串須要進行詞法分析,編譯優化再執行的過程,經過tokenize可讓機器能理解咱們的數據。Sizzle也經過兩個嘗試,一是縮小上下文,一是創建初始集合seed集合,從而儘量的去縮小查詢的範圍,儘量的提升查詢的性能。

相關文章
相關標籤/搜索