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


主要流程與正則

表達式分塊

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g;

這個正則比較長,主要是用來分塊和一步預處理。

1、

2、

3、

4、

'div#test + p > a.tab'    --> ['div#test','+','p','>','a.tab']

從表達式提取出相應的類型:

這個需要對應jQuery的選擇器來看,共7種
ID選擇器,CLASS選擇器,TAG選擇器,ATTR屬性選擇器,CHILD子元素選擇器,PSEUDO偽類選擇器,POS位置選擇器

判斷的方法還是正則,具體正則如下:

ID    : /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
CLASS : /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
NAME : /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
ATTR : /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
TAG : /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
CHILD : /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
POS : /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/

 

ID:

CLASS:

NAME:

TAG:

ATTR:

POS:

PSEUDO:

正則小提示:

?非貪婪量詞
\3匹配分
?=正向預查

這些正則可能一開始不好看,但是對應到具體的jQuery選擇器就比較好理解了:

POS ——  :first :nth() :last :gt :lt :even :odd  這些是Sizzle新加的,跟CSS無關

其他的倒是跟CSS基本無異,需要注意的是,由於PSEUDO的存在,同一個表達式可能同時匹配多個類型,這個后面的filter部分會提到。

 

上面的正則字符串保存在Expr的match屬性中,

Expr = {
match:{
//ID:....
}
}


這部分正則並沒有直接使用,進行了進一步的處理
第一、每個字符串后面都增加了一個判斷,用來確保匹配結果,末尾不包含)或者}

/#((?:[\w\u00c0-\uFFFF\-]|\\.)+))/變成/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?![^\[]*\])(?![^\(]*\)/


第二、同時Sizzle會檢測轉義字符,因此各部分頭部都增加了一個捕獲組用來保存目標字符串前面的部分,
在這一步的時候,由於在頭部增加了一分組,因此原正則字符串中的\3等符號必須順次后移。

/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?![^\[]*\])(?![^\(]*\)/變成
/(^(?:.|\r|\n)*?)#((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?![^\[]*\])(?![^\(]*\)/

 

/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/變成
/(^(?:.|\r|\n)*?):((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\3\))?(?![^\[]*\])(?![^\(]*\)/

對應到源碼中就是:

var    fescape = function(all, num){
return "\\" + (num - 0 + 1);
};

for ( var type in Expr.match ) {
Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

Expr.leftMatch 中保存的是處理過后的正則部分,這么做的另一個好處就是避免每次匹配都去創建一個新的RegExp對象

 

回到主流程

函數介紹:

var Sizzle = function( selector, context, results, seed ){}
Sizzle有四個參數:
  selector :選擇表達式
  context :上下文
  results  :結果集
  seed     :候選集

實例說明:

Sizzle('div',#test,[#a,#b],[#c,#d,#e])就是在集合[#c,#d,#e]中查找滿足條件(在#test范圍中並標簽名為div)的元素,然后將滿足條件的結果存入[#a,#b]中,假設滿足條件的有#d,#e,最后獲得就是[#a,#b,#d,#e]。

代碼示例:

var Sizzle = function( selector, context, results, seed ){
var soFar = selector,
extra ,//extra用來保存並聯選擇的其他部分,一次只處理一個表達式
parts = [],
m;
do {
chunker.exec( "" ); //這一步主要是將chunker的lastIndex重置,當然直接設置chunker.lastIndex效果也一樣
m = chunker.exec( soFar );
if ( m ) {
soFar = m[3];
parts.push( m[1] );
if ( m[2] ) { //如果存在並聯選擇器,就中斷,保存其他的選擇器部分。
extra = m[3];
break;
}
}
} while ( m );
}

對於'div#test + p > a.tab'
parts結果就是['div#test','+','p','>','a.tab']

分塊之后,下一步就是決定的選擇器的順序,可以對照(一)的說明,構建兩個分支:

if ( parts.length > 1 && origPOS.exec( selector ) ) {
//自左向右,判斷標准就是存在關系選擇符同時有位置選擇符,因為如果只是類似div#test的選擇表達式,就不存在順序的問題。

}else{
//其他,自右向左
}

【說明:origPOS保存的是Expr.match.POS,源碼901行】

先看普通的(自右向左)情況
然后就是ID的問題,第一個選擇表達式含有id就重設context,
當存在context的時候【沒有的話就不用找了,因為肯定沒結果】,

在重設了contexr之后,既然是自右向左,第一步就是獲取等待過濾的集合,

ret = seed ?{ expr: parts.pop(), set: makeArray(seed) } :Sizzle.find( parts.pop(), context);//Sizzle.find負責查找
set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;//Sizzle.filter負責過濾


有候選集seed的時候直接獲得候選集,沒有的時候獲取最右邊一個選擇符的結果集。

后面的過程就是依次取出parts中的選擇符,在set中查找,過濾,直到全部查完

        while ( parts.length ) {
Expr.relative[ cur ]( checkSet, context, contextXML );//context代表上下文,並非源碼中的參數
}

 

實例說明:

['div#test','+','p','>','a.tab']處理流程
第一步、沒有候選集seed,第一項'div#test'中含有id信息,最后一項'a.tab'中不含有id信息,因此重設content=Sizzle.find('div#test',document)
第二步、剩余部分為['+','p','>','a.tab'],沒有候選集seed,先獲取等待過濾[標簽名為a]的集合A,在集合A中過濾類名為tab的集合B
第三步、剩余部分為['+','p','>'],進行基於關系的過濾,這是一個逆向過程,假設第二步中的B=[#a,#b,#c,#d],先在查找直接父節點是p的元素,獲得集合
    C = [#a,#b,false,false],然后獲取緊挨第一步中content的元素,獲得集合D = [#a,false,false,false]
第四步、取得D中不為false的部分,獲得此次選擇的集合E=[#a];並入結果集result中。
第五步、按照上面的規則,處理並聯選擇表達式的第二部分。

 

討論

關於context的選擇

在沒有候選集,需要重設ID情況有那些呢?
  1、div#id_1 a#id_2
  2、div#id_1 a
  3、div a#id_2
Sizzle中,只有情況(2)下才去設置context

第二步中、關系選擇符“+”和“~”表示的是同層級的關系,因此,context【查找范圍】會被設置成context.parentNode

實例說明:

<body>
<div id="test_a">
<p class="tab" id="a1">a1</p>
<p class="tab" id="a1">a2</p>
<p class="tab" id="a1">a3</p>
</div>
<div id="test_b">
<p class="tab" id="b1">b1</p>
<p class="tab" id="b1">b2</p>
<p class="tab" id="b1">b3</p>
</div>
</body>

選擇表達式'div#test_a ~ div'
第一步重設context為div#test_a
第二步中如果直接執行(div#test_a).getElemnetsByTagName('div')顯然是沒有結果的,此時操作根本就是錯誤的。
因此,應該執行的是(div#test_a).parentNode.getElemnetsByTagName('div').然后再進行第三步。



接下來是Sizzle.find流程分析:《Sizzle引擎--原理與實踐(三)

 

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




免責聲明!

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



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