jQuery中的Sizzle引擎分析

來源:互聯網
上載者:User

標籤:elementmatchers

我分析的jQuery版本是1.8.3。Sizzle代碼從3669行開始到5358行,將近2000行的代碼,這個引擎的版本還是比較舊,最新的版本已經到v2.2.2了,代碼已經超過2000行了。並且還有個專門的Sizzle首頁。

從一個demo開始,HTML代碼如下:

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

<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>

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

然後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‘)後發生的過程:

650) this.width=650;" src="http://images2015.cnblogs.com/blog/211606/201512/211606-20151227135830046-4612755.png" style="border:0px;margin-top:10px;" />

 

一、jQuery對象

對象是需要new一下才行的,但是jQuery只要$("xxx")後,就產生了一個對象。

1)jQuery建構函式

在第42行,將會返回一個new對象:

jQuery = function( selector, context ) {    // The jQuery object is actually just the init constructor ‘enhanced‘    return new jQuery.fn.init( selector, context, rootjQuery );}

 

2)jQuery對象結構

根據上面的返回對象的圖中可以看到:

a. 對象的原型屬性__proto__指向的是函數jQuery的原型屬性prototype。__proto__ 是內部 [ [Prototype ]] ,原型鏈就是通過這個屬性來實現的。

b. 索引是0和1的,其實是瀏覽器中的原生對象,我們可以搞個簡單的選取器來驗證,例如$("#radio1"),代碼將會執行到140行

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

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;

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

 

二、select函數

5116行的select函數是引擎的入口:

1)在這裡引用了詞法分析函數tokenize。

2)當tokenize返回的Token集合數組只有一個的時候,將會尋找種子合集【通過一些原生DOM介面可擷取到】,在5147行中可以看到:

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

/*完整的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    ))) {    //省略邏輯....  }}

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

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函數的流程

650) this.width=650;" src="http://images2015.cnblogs.com/blog/211606/201512/211606-20151227141508999-1569539853.png" style="border:0px;margin-top:10px;" />

中有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選擇文法轉變成具體的匹配函數的過程也是編譯。

650) this.width=650;" src="http://images2015.cnblogs.com/blog/211606/201512/211606-20151227175715812-1682379792.png" style="border:0px;margin-top:10px;" />

 

1)matcherFromTokens

5080行的compile函數通過引用4931行的matcherFromTokens函數擷取Token集合對應的匹配器,引用代碼如下:

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

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 }

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

返回了兩個函數數組,對應上面的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)中有“,”號,所以是兩組大匹配器。

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

for (;(elem = elems[i]) != null; i++) {  //省略邏輯...  for (j = 0; (matcher = elementMatchers[j]); j++) {    if (matcher(elem, context, xml)) {      results.push(elem);      break;    }  }  //省略邏輯...}

650) this.width=650;" src="/img/fz.gif" alt="複製代碼" style="margin-top:10px;border:none;" />

 

大致過程就是這樣,裡面還有很多細節地方,這裡就不討論了,有興趣的可以自己去打打斷點玩玩。


jQuery中的Sizzle引擎分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.