我分析的jQuery版本是1.8.3。Sizzle代碼從3669行開始到5358行,將近2000行的代碼,這個引擎的版本仍是比較舊,最新的版本已經到v2.2.2了,代碼已經超過2000行了。而且還有個專門的Sizzle主頁。css
從一個demo開始,HTML代碼以下:node
<div id="grand_father"> <div id="father"> <div id="child1" class="child">子集1</div> <div id="child2" class="child">子集2</div> <div id="child3" class="child">子集3</div> <input type="radio" id="radio1"/> </div></div>
而後JavaScript代碼以下:數組
var $nodes = $('div + input[type="radio"],div.child:first-child'); console.log($nodes);
1)返回的是一個jQuery對象,以下圖所示,而且匹配到了兩個標籤,一個div和radio,瀏覽器
2)右邊的div在0的位置,radio在1的位置,這說明jQuery的選擇器匹配是從右往左的!閉包
下面看一個流程圖,當我編寫了$('div + input[type="radio"],div.child:first-child')後發生的過程:ide
對象是須要new一下才行的,可是jQuery只要$("xxx")後,就生成了一個對象。函數
1)jQuery構造函數this
在第42行,將會返回一個new對象:spa
jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }
2)jQuery對象結構prototype
根據上面的返回對象的圖中能夠看到:
a. 對象的原型屬性__proto__指向的是函數jQuery的原型屬性prototype。__proto__ 是內部 [ [Prototype ]] ,原型鏈就是經過這個屬性來實現的。
b. 索引是0和1的,實際上是瀏覽器中的原生對象,咱們能夠搞個簡單的選擇器來驗證,例如$("#radio1"),代碼將會執行到140行
elem = document.getElementById(match[2]);// Check parentNode to catch when Blackberry 4.6 returns// nodes that are no longer in the document #6963if (elem && elem.parentNode) { // Handle the case where IE and Opera return items // by name instead of ID if (elem.id !== match[2]) { return rootjQuery.find(selector); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem;}this.context = document;this.selector = selector;return this;
5116行的select函數是引擎的入口:
1)在這裏引用了詞法分析函數tokenize。
2)當tokenize返回的Token集合數組只有一個的時候,將會尋找種子合集【經過一些原生DOM接口可獲取到】,在5147行中能夠看到:
/*完整的find在4089行,簡易的find以下: Expr.find = { 'ID': context.getElementById, 'CLASS': context.getElementsByClassName, 'NAME': context.getElementsByName, 'TAG': context.getElementsByTagName } */if ((find = Expr.find[type])) { // Search, expanding context for leading sibling combinators if ((seed = find( token.matches[0].replace(rbackslash, ""), rsibling.test(tokens[0].type) && context.parentNode || context, xml ))) { //省略邏輯.... } }
3)經過compile編譯函數,生成Token集合數組對應的匹配器,匹配後返回結果。
高級的瀏覽器會直接使用querySelectorAll方法選擇匹配。而低級的瀏覽器IE6或IE7等,就只能進入到jQuery的Sizzle引擎進行匹配。
爲了調試方便,我將5182行的代碼修改爲「!document.querySelectorAll」,讓高級瀏覽器也進入Sizzle引擎中匹配。
1)Token格式
4684行的tokenize函數最終返回的是Token集合數組,Token是一個String對象,格式以下:
String{0:'字符1',1:'字符2',....., type:'對應的Token類型【TAG,ID,CLASS,ATTR,CHILD,PSEUDO,NAME,>,+,空格,~】', matches:'正則匹配到的一個結構'}
type類型根據4150行的relative對象和4230行的新航道託福filter對象中的key值獲取。
2)返回的結果
'div + input[type="radio"],div.child:first-child'返回的數組以下:
上面返回的順序是從左往右,先input,而後是div。
3)tokenize函數的流程
上圖中有4個關係符號:
Expr.relative = { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }
結合上面的HTML結構:
1)grand_father與child1屬於祖宗與後代關係(空格表達)
2)father與child1屬於父子關係,也算是祖先與後代關係(>表達)
3)child1與child2屬於臨近兄弟關係(+表達)
4)child1與child2,child3都屬於普通兄弟關係(~表達)
把高級規則轉換成底層實現就叫編譯,好比高級語言到機器語言的過程就是編譯。一樣把抽象的css選擇語法轉變成具體的匹配函數的過程也是編譯。
1)matcherFromTokens
5080行的compile函數經過引用4931行的matcherFromTokens函數獲取Token集合對應的匹配器,引用代碼以下:
1 i = group.length;//從右往左2 while (i--) {3 cached = matcherFromTokens(group[i]);4 if (cached[expando]) {5 setMatchers.push(cached);6 } else {7 elementMatchers.push(cached);8 }9 }
返回了兩個函數數組,對應上面的Token集合數組,因爲是從右往左,因此與上面的Token集合數組反過來。【在4979行console.log(matchers)】
打開第一個值,會發現裏面還嵌套着不少閉包,閉包裏面又有閉包,這就是前面所說的大的匹配函數:
matcherFromTokens最後會引用4803行的elementMatcher,將上面的數組做爲參數傳遞過去。
上面示例代碼的第7行就在將函數插入到elementMatchers數組中。再傳遞給下面的matcherFromGroupMatchers函數。
2)matcherFromGroupMatchers
再引用4983行的matcherFromGroupMatchers函數生成終極匹配器,返回匹配結果。
這個函數將會return出來的一個curry化的函數,也就是4986行的superMatcher函數。
在4995行,superMatcher函數會根據參數seed 、expandContext和context肯定一個起始的查詢範圍:
elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context )
有多是直接從seed種子集合中獲取,也有可能在context或者context的父節點範圍內。
這裏的context是「document」,也就是整個DOM樹【在5003行console.log(elems)】,elems結構以下:
能夠看出若是事先定義了content,就會把範圍縮小不少,利於匹配,例如jQuery能夠這樣寫:
$('div + input[type="radio"],div.child:first-child', $('#grand_father'))
在5007行開始過濾,elementMatchers參數就是上面返回的大匹配器。
之因此用for是由於選擇器(div + input[type="radio"],div.child:first-child)中有「,」號,因此是兩組大匹配器。
for (;(elem = elems[i]) != null; i++) { //省略邏輯... for (j = 0; (matcher = elementMatchers[j]); j++) { if (matcher(elem, context, xml)) { results.push(elem); break; } } //省略邏輯...}
大體過程就是這樣,裏面還有不少細節地方,這裏就不討論了,有興趣的能夠本身去打打斷點玩玩。