jQuery 2.0.3 源碼分析Sizzle引擎 - 超級匹配

 聲明:本文爲原創文章,如需轉載,請註明來源並保留原文連接Aaron,謝謝!javascript

經過Expr.find[ type ]咱們找出選擇器最右邊的最終seed種子合集html

經過Sizzle.compile函數編譯器,咱們把tokenize詞法元素編譯成閉包函數java

超級匹配superMatcher,用佳的方式從seed種子集合篩選出須要的數據node

也就是經過seed與compile的匹配,得出最終的結果了web

 


 superMatcher 函數 數組

這個方法並非一個直接定義的方法,經過matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出來的一個curry化的函數,可是最後執行起重要做用的是它。數據結構

注意是compile()().閉包

    compile( selector, match )(
        seed,
        context,
        !documentIsHTML,
        results,
        rsibling.test( selector ) && testContext( context.parentNode ) || context
    );

superMatcher方法會根據參數seed 、expandContext和context肯定一個起始的查詢範圍函數

elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),

有多是直接從seed中查詢過濾,也有可能在context或者context的父節點範圍內。若是不是從seed開始,那隻能把整個DOM樹節點取出來過濾了,把整個DOM樹節點取出來過濾了,它會先執行Expr.find["TAG"]( "*", outermost )這句代碼等到一個elems集合(數組合集)優化

context.getElementsByTagName( tag );

能夠看出對於優化選擇器,最右邊應該寫一個做用域的搜索範圍context比較好

 

開始遍歷這個seed種子合集了

while ( (matcher = elementMatchers[j++]) ) {
    if ( matcher( elem, context, xml ) ) {
        results.push( elem );
        break;
    }
}

elementMatchers:就是經過分解詞法器生成的閉包函數了,也就是「終極匹配器」

爲何是while?

前面就提到了,tokenize選擇器是能夠用過 「,」逗號分組 group,因此就就會有個合集的概念了

matcher就獲得了每個終極匹配器

經過代碼很能看出來matcher方法運行的結果都是bool值

對裏面的元素逐個使用預先生成的matcher方法作匹配,若是結果爲true的則直接將元素堆入返回結果集裏面。

 


matcher

matcher 就是 elementMatcher函數的包裝

整個匹配的核心就在這個裏面了

        function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }
            return true;
        } :

咱們先來回顧下這個matchers的組合原理

這個地方是最繞的,也是最暈的,因此仍是要深刻的理解才行哦

先上個簡單的流程圖:

畫的很差 哈哈

 

執行分解:

第一步:

div > p + div.aaron input[type="checkbox"]

從右邊剝離出原生API能使用的接口屬性

context.getElementsByTagName( input )

因此找到了input ,由於只能夠用 tag是查詢,可是此時結果是個合集,引入seed的概念,稱之爲種子合集

 

第二步:

div > p + div.aaron [type="checkbox"]'

重組選擇器,踢掉input,獲得新的tokens詞法元素哈希表

 

第三步:

經過matcherFromTokens函數,而後根據 關係選擇器 【'>',"空","~","+"】拆分分組,由於DOM中的節點都是存在關係的,因此引入

Expr.relative -> first:true 兩個關係的「緊密」程度, 用於組合最佳的篩選

一次按照以下順序解析而且編譯閉包函數

編譯規則:div > p + div.aaron [type="checkbox"]'
編譯成4組閉包函數,而後在先後在合併組合成一組
div >

p +

div.aaron 

input[type="checkbox"]

 

先看構造一組編譯函數


A: 抽出div元素, 對應的是TAG類型
B: 經過Expr.filter找到對應匹配的處理器,返回一個閉包處理器
如:TAG方法

        "TAG": function( nodeNameSelector ) {
            var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
            return nodeNameSelector === "*" ?
                function() { return true; } :
                function( elem ) {
                    return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
                };
        },

C:將返回的curry方法放入到matchers匹配器組中,繼續分解

D:抽出子元素選擇器 '>' ,對應的類型 type: ">" 

E:經過Expr.relative找到elementMatcher方法分組合並多個詞素的的編譯函數

    function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }

因此這裏其實就是執行了各自Expr.filter匹配中的的判斷方法了,看到這裏matcher方法原來運行的結果都是bool值,

因此這裏只返回了一個組合閉包,經過這個篩選閉包,各自處理本身內部的元素

F:返回的這個匹配器仍是不夠的,由於沒有規範搜索範圍的優先級,因此這時候還要引入addCombinator方法

G:根據Expr.relative -> first:true 兩個關係的「緊密」程度
若是是是親密關係addCombinator返回

function( elem, context, xml ) {
    while ( (elem = elem[ dir ]) ) {
        if ( elem.nodeType === 1 || checkNonElements ) {
            return matcher( elem, context, xml );
        }
    }
}

因此可見若是是緊密關係的位置詞素,找到第一個親密的節點,立馬就用終極匹配器判斷這個節點是否符合前面的規則

 

這是第一組終極匹配器的生成流程了

可見過程極其複雜,被包裝了三層

依次

addCombinator
elementMatcher
Expr.relative

 

三個方法嵌套處理出來的結構

 

而後繼續分解下一組,遇到關係選擇器又繼續依照以上的過程分解

可是有一個不一樣的地方,下一個分組會把上一個分組給一併合併了

因此整個關係就是一個依賴嵌套很深的結構

最終暴露出來的終極匹配器其實只有一個閉包,可是有內嵌很深的分組閉包了

依照從左邊往右依次生成閉包,而後把上一組閉包又push到下一組閉包

就跟棧是一種後進先出的數據結構同樣處理了

因此在最外層也就是

type=["checkbox"]

 


咱們回到superMatcher方法的處理了

在遍歷seed種子合集,依次匹配matchers閉包函數,傳入每個seed的元素與之匹配(這裏就是input),在對應的編譯處理器中經過對input的處理,找到最優匹配結果

function( elem, context, xml ) {
    var i = matchers.length;
    while ( i-- ) {
        if ( !matchers[i]( elem, context, xml ) ) {
            return false;
        }
    }
    return true;
} :

這裏注意了,是i--,從後往前找
因此第一次開始匹配的就是

check: "checkbox"
name: "type"
operator: "="

那麼就找到對應的Attr處理方法

//屬性元匹配器工廠
//name :屬性名
//operator :操做符
//check : 要檢查的值
//例如選擇器 [type="checkbox"]中,name="type" operator="=" check="checkbox"
Expr.filter["ATTR"] = function( name, operator, check ) {
 
  //返回一個元匹配器
  return function( elem ) {
    //先取出節點對應的屬性值
    var result = Sizzle.attr( elem, name );
 
    //看看屬性值有木有!
    if ( result == null ) {
      //若是操做符是不等號,返回真,由於當前屬性爲空 是不等於任何值的
      return operator === "!=";
    }
    //若是沒有操做符,那就直接經過規則了
    if ( !operator ) {
      return true;
    }
 
    //轉成字符串
    result += "";
 
 
    return 
      //若是是等號,判斷目標值跟當前屬性值相等是否爲真
      operator === "=" ? result === check :
 
      //若是是不等號,判斷目標值跟當前屬性值不相等是否爲真
      operator === "!=" ? result !== check :
 
      //若是是起始相等,判斷目標值是否在當前屬性值的頭部
      operator === "^=" ? check && result.indexOf( check ) === 0 :
 
      //這樣解釋: lang*=en 匹配這樣 <html lang="xxxxenxxx">的節點
      operator === "*=" ? check && result.indexOf( check ) > -1 :
 
      //若是是末尾相等,判斷目標值是否在當前屬性值的末尾
      operator === "$=" ? check && result.slice( -check.length ) === check :
 
      //這樣解釋: lang~=en 匹配這樣 <html lang="zh_CN en">的節點
      operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
 
      //這樣解釋: lang=|en 匹配這樣 <html lang="en-US">的節點
      operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
      //其餘狀況的操做符號表示不匹配
      false;
  };
},

 

Sizzle.attr( elem, name )

傳入elem元素就是seed中的input元素,找到是否有'type'類型的屬性,

好比

<input type="text">"

因此第一次匹配input就出錯了,返回的type是text,而不是咱們須要的'checkbox'
這裏返回的結果就是false,因此整個以後的處理就直接return了

 


 

繼續拿出第二個input

繼續上一個流程,這時候發現檢測到的屬性

var result = Sizzle.attr( elem, name );
result: "checkbox"

此時知足第一條匹配,而後繼續 i = 0

!matchers[i]( elem, context, xml )

找到第0個編譯函數

addCombinator
while ( (elem = elem[ dir ]) ) {
    if ( elem.nodeType === 1 || checkNonElements ) {
        outerCache = elem[ expando ] || (elem[ expando ] = {});
        if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
            if ( (data = cache[1]) === true || data === cachedruns ) {
                return data === true;
            }
        } else {
            cache = outerCache[ dir ] = [ dirkey ];
            cache[1] = matcher( elem, context, xml ) || cachedruns;
            if ( cache[1] === true ) {
                return true;
            }
        }
    }
}
若是是不緊密的位置關係
那麼一直匹配到true爲止
如祖宗關係的話,就一直找父親節點直到有一個祖先節點符合規則爲止
直接遞歸調用
matcher( elem, context, xml )

其實就是下一組閉包隊列了,傳入的上下文是 div.aaron,也就是<input type="checkbox"的父節點

function (elem, context, xml) {
                var i = matchers.length;
                //從右到左開始匹配
                while (i--) {
                    //若是有一個沒匹配中,那就說明該節點elem不符合規則
                    if (!matchers[i](elem, context, xml)) {
                        return false;
                    }
                }
                return true;
        }

依照上面的規則,這樣遞歸下去了,一層一層的匹配

 


可見它原來不是一層一層往下查,卻有點倒回去向上作匹配、過濾的意思。Expr裏面只有find和preFilter返回的是集合。

儘管到這裏暫時還帶着一點疑問,就是最後它爲何用的是逐個匹配、過濾的方法來獲得結果集,可是我想Sizzle最基本的「編譯原理」應該已經解釋清楚了。

哥們,別光看不頂啊!

相關文章
相關標籤/搜索