Sizzle引擎--原理與實踐(一)


  大家都知道,Sizzle是jQuery的御用選擇器引擎,是jQuery作者John Resig寫的DOM選擇器引擎,速度號稱業界第一。另外,Sizzle是獨立的一部分,不依賴任何庫,如果你不想用jQuery,可 以只用Sizzle。所以單獨拿出來特別對待。在Prototype1.7中,選擇器也采用了Sizzle,不過版本有點老,所以我去Sizzle網站搞了一份新的 下來,於是下面分析的時候使用的是最新版的Sizzle.js

預先說明

在分析初期為了保證各個瀏覽器的結果一致,不考慮原生getElementsByClass以及querySelectorAll的影響,同時忽略XML類型,因此作一下處理:
源碼1292行,

(function(){
...
})()

改為:

(function(){
return false;
...
})()

源碼1140行

if ( document.querySelectorAll ) {
...
}

改為:

if ( document.querySelectorAll && false) {
...
}

至於其他的瀏覽器兼容處理部分,會在初步分析中一並涉及。

預備知識

CSS選擇器jQuery選擇器。由於jQuery選擇器的形式來自CSS,但是在CSS的基礎上又增加了很多新的選擇表達式,因此,一切以jQuery選擇器為基礎。

實例開題:

對於一個選擇表達式:

div#outer > ul , div p:nth(1),form input[type="text"]

關於分塊的討論

這里面包含三個並聯的選擇符,我們怎么處理?
解決方案:
1、可以用split(',')來處理,但是這樣只是單純的分割出來了,並不能獲得更多的信息。
2、所以我們采用正則來分塊,缺點是可讀性以及效率的問題,優點是可以提取一些必要的信息,進行預處理。

所以jquery采取的是正則,但是並沒有完全分塊,而是一部分一部分的取。對於上面的例子我們看看怎么分塊。

div#outer > ul , div p:nth(1),form input[type="text"]先分離出來div#outer > ul,處理完畢后再分離出來div p:nth(1),處理完畢后再分離出來form input[type="text"],最后合並三部分的結果

關於選擇順序的討論

這里需要記得jQuery選擇器的作用

一個典型的例子:

body div p:first-child
body div p:first


這兩個的含義完全不一樣,如果可以用括號這么寫的話,可以改成:

body div p:first-child --> body div (p:first-child)
body div p:first --> (body div p):first


first-child是用來限定p的,可以算是p的一個屬性,
body div p:first是用來限定body div p結果集的,可以算是body div p結果集的一個方法。
body div p:first == (body div p).eq(0)


類似的情況還有jQuery自定義的幾個位置查詢表達式,所以
情況一、body div p:first

  自左向右的查詢。先找到body,獲得集合A,然后在集合A中查找div獲得集合B,在集合B中查找p獲得集合C,最后取集合C的第一個元素,得最終結果XX

情況二、body div p:first-child  

  自右向左的查詢,先找到p:first-child獲得集合A1,然后判斷祖先元素里面是否有div獲得集合B1,然后判斷祖先元素里面是否有body獲得集合C1,得最終結果C1

  對比上面的兩個過程,相較於過程一,過程二更像是一個過濾的過程,因此,Sizzle最大的亮點是自右向左過濾。

  另外,為了提高查詢效率,最重要的就是縮小查找范圍和減少遍歷次數。
一個典型的例子是:
div p
  情況一、先找到p,獲得集合A,然后判斷祖先元素里面是否有div獲得集合B,得最終結果B
div#a p
  #a一般只會有一個,所以情況一里面p的范圍太大了,所以如果第一個選擇表達式里面含有id,Sizzle總是先查找第一個,從而縮小查找范圍
  情況二、先找到div#a,獲得單個元素A1,然后再A1的環境中查找p,得最終結果B1

因此,最終的過程就變成

第一、分割表達式
第二、查找元素
第三、過濾元素(過濾分兩種,1、通過元素自身屬性過濾 2、通過元素之間關系過濾。)

因此可以獲得Sizzle的大致代碼結構:
Sizzle引擎基本結構
主要流程:window.Sizzle = function(){};
查找元素:Sizzle.find = function(){};
過濾元素:Sizzle.filter = function(){};

定義用到的一些查找方式和過濾條件:Sizzle.selectors = {};
Sizzle.selectors經常要用到,於是給它一個簡短的名字,就叫做Expr

於是Sizzle的代碼框架如下:

window.Sizzle = function(){
//主要負責分塊和主線流程,通過元素之間關系過濾也被放在這個部分
};
Sizzle.find = function(){
//查找元素
};
Sizzle.filter = function(){
//過濾元素,主要處理通過元素自身屬性過濾
};
Sizzle.selectors = {
//命名空間,以及定義一些變量和特異方法
};
var Expr = Sizzle.selectors;//別稱

剩下的問題就是怎么獲得我們需要的元素集合

 

關於查找元素的初步討論

我們先看,怎么找元素,瀏覽器原生只有三種查找元素的方式
(文章開頭我們已經假設初步所有的瀏覽器原生都不支持getElementsByClassName,雖然大部分都支持):
getElementById、getElementsByName以及getElementsByTagName。

問題一:
當我們遇到一個選擇表達式的時候,怎么判斷這個選擇表達式是什么類型的。
解決方案:
  依次檢測這個表達式的類型,獲得匹配的類型,注意,一旦獲得了匹配的類型,就不會繼續匹配了。
  至於先匹配哪個類型,查找原則就是盡可能的縮小檢索范圍(特異性越高,檢索范圍就越小)。一般情況下,ID數量小於NAME數量,NAME數量又小於TAG數量。因此判斷順序就是['ID','NAME','TAG']
實例說明:
1、input[name="test"],先查找[name="test"],此時雖然還可以繼續查找input,但是沒有那個必要了,因為如果查詢input之后再去取交集,會破壞程序的結構。
Sizzle的處理方式就是:先查找[name="test"]獲得集合A,然后在A中過濾input,input被作為一個過濾條件丟到過濾部分去處理。

2、input#demo[name="test"] 由於ID的優先級比NAME高,所以先查找#demo獲得集合A,然后在A中過濾input[name="test"],input[name="test"]被作為一個過濾條件丟到過濾部分去處理。

關於過濾元素的初步討論

過濾是一個條件一個條件來進行的,對於一個集合A以及過濾表達式

expr='[class="test"][rel=1]:first-child'

在A中過濾[class="test"]得到集合B,此時過濾表達式expr='[rel=1]:first-child'
在B中過濾[rel=1]得到集合C,此時過濾表達式expr=':first-child'
在C中過濾:first-child得到集合D,此時過濾表達式expr='',過濾完畢

【說明,上面的順序不代表真實的順序,是指為了說明“過濾是一個條件一個條件來進行的”這句活而已】
過濾分兩步:
第一、預過濾Expr.preFilter,處理兼容問題以及格式轉換,決定下一步的走向
第一、最終過濾Expr.filter,這一步的形式都是一樣的,最終返回的都是一個布爾值。

過濾方式:

假設第一輪的篩選集合:A = ['#1','#2','#3','#4','#5','#6']
過濾條件為isMatch
滿足條件的集合為C = ['#1','#2','#3']

第一、

var B = [];
for( i = 0; (item = A[i]) != null; i++ ){
A[i] = isMatch(item);
}
for( i = 0; i < A.length; i++ ){
if(A[i]){
B.push(A[i]);
}
}

1、['#1','#2','#3','#4','#5','#6']
2、['#1','#2','#3',false,false,false]
3、['#1','#2','#3']

第二、

var B = [];
for( i = 0; (item = A[i]) != null; i++ ){
if(isMatch(item)){
B.push(A[i]);
}
}


接下來是主流程以及必須正則分析:《Sizzle引擎--原理與實踐(二)

 

轉載請注明來自小西山子【http://www.cnblogs.com/xesam/
本文地址:http://www.cnblogs.com/xesam/archive/2012/02/15/2352466.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM