這篇博客難度太大,跟前端開發其實沒什么關系,如果你想成為大牛,那就去了解下吧。如果你還不想,那可以忽略,畢竟面試官也不會問到這里來,因為他也不太懂。呵呵。
Sizzle引擎是jQuery的選擇器,它大部分操作都是從右到左進行選擇,特殊選擇符會從左到右。用戶輸入$("div"),$("div p.class"),$("div [attr=val] :checked")等各種復雜的選擇符,它都能選擇到用戶想要取到的元素節點。
Sizzle的整體結構如下:
(1)Sizzle主函數,里面包含選擇符的切割,內部循環調用主查找函數,主過濾函數,最后是去重過濾。
(2)其他輔助函數,如 uniqueSort, matches ,matchesSelector。
(3)Sizzle.find主查找函數
(4)Sizzle.filter主過濾函數
(5)Sizzle.selectors 包含各種匹配用的正則,過濾用的正則,分解用的正則,預處理函數,過濾函數。
(6)根據瀏覽器的特征設計makeArray,sortOrder,contains等方法。
(7)根據瀏覽器的特征重寫Sizzle.selectors中的部分查找函數,過濾函數,查找次序。
(8)若瀏覽器支持querySelectorAll,那么用它重寫Sizzle,將原來的Sizzle作為后備方案包裹在新的Sizzle里面。
(9)其他輔助函數,如:isXML,posProcess。
Sizzle.find主查找函數和Sizzle.filter過濾函數實現原理:
對js原生的4大查找函數,getElementById(針對id),getElementsByName(針對name),getElementsByTagName(針對標簽名tagName,比如div,p),getElementsByClassName(針對class),進行一層封裝,瀏覽器支持的話,就返回數組或者NodeList,不支持的,就返回undefined。
這里需要講一下種子集,
種子集就是通過最右邊的選擇器組得到的元素集合。比如:"div.aaa span.bbb",最右邊的選擇器組就是"span.bbb",這時引擎會根據瀏覽器的支持情況選擇getElementsByTagName(span)或getElementsByClassName(bbb)得到一組元素,然后再通過class(bbb)或tagName(span)進行過濾,這時得到的集合就是種子集。種子集是分兩步篩選出來的,首先,通過Sizzle.find得到一個大體的結果,然后通過Sizzle.filter過濾。那我們是先取span,還是.bbb呢?這里有一個准則,要確保我們后面的映射集(當我們取得種子集后,會將種子集復制一份,這就是映射集)最小。為了達到此目的,這里有一個優化,原生選擇器的調用順序被放在一個Sizzle.selectors.order的數組中,對於低版本瀏覽器,其順序為id,name,tagName,對於支持getElementsByClassName的瀏覽器,順序為id,class,name,tagName。因為id只返回一個元素,class與樣式相關,不是每個元素都有這個類名的,name屬性使用到的幾率比較少,而tagName排除的元素比較少。所以Sizzle.find就會根據Sizzle.selectors.order數組,依次調用正則,從最右的選擇器中切下需要的部分,找到粗糙的節點集合。(針對"span.bbb",id調用正則時,找不到,然后class,調用正則,找到.bbb,因此就調用getElementsByClassName(bbb)得到一組數據,最后通過Sizzle.filter過濾取到的數據,過濾條件是tagName(span))
映射集,
當我們取得種子集后,會將種子集復制一份,這就是映射集。種子集是由一個選擇器組選出來的,這時如果選擇符不為空(前面是"div.aaa"),必然往左就是關系選擇器(父親,兄弟,后代),關系選擇器會讓引擎去選取其兄長或父親,把這些元素置換到映射集對等的位置上(個數不變,因此映射集和種子集的數量總是相當)。然后到下一個選擇器組時("div.aaa"),就是過濾操作了。主過濾函數Sizzle.filter會調用Sizzle.selectors下的N個過濾函數對這些元素進行檢測,將不符合的元素替換為false。因此到最后要去重排時,映射集是一個包含布爾值與元素節點的數組。
下面就是根據瀏覽器的特征進行優化:
IE6,7下getElementById有bug。需要重寫。
IE6-IE8下,Array.prototype.slice.call無法切割NodeList。需要重寫makeArray。jQuery中直接用循環,把類數組轉化成數組。
IE6-IE8下,getElementsByTagName("*"),會混雜注釋節點。
這里大家可能會提出現在有些瀏覽器支持querySelectorAll方法,這是原生的,可以用來選擇元素。
在Sizzle中,當瀏覽器支持querySelectorAll方法時,會重寫Sizzle。但是在重寫時,會根據不同情況提出各種提速方案:
(1)getElementById還是比querySelectorAll速度快,因為getElementById只返回一個元素,而且內部做了緩存,但是querySelectorAll會返回擁有這個id值的多個元素,盡管頁面id一般是唯一的,但如果出現了多個同樣id的情況下,getElementById還是只返回一個元素,而querySelectorAll會返回多個。
(2)getElementsByTagName內部也使用了緩存,而且返回的是NodeList對象,querySelectorAll返回的是一個StaticNodeList對象,前面是動態的,后面是靜態的。區別在於:document.getElementsByTagName("div") == document.getElementsByTagName("div"),返回真,document.querySelectorAll("div") == document.querySelectorAll("div"),返回false.返回true的,意味着它們拿到的同是cache引用。返回false意味着每次返回都是不一樣的object。數據表明:創建一個動態的NodeList對象比創建一個靜態的StaticNodeList對象快90%.
加油!