天天看點

jQuery 2.0.3 源碼分析Sizzle引擎 - 超級比對

 聲明:本文為原創文章,如需轉載,請注明來源并保留原文連結Aaron,謝謝!

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

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

超級比對superMatcher,用佳的方式從seed種子集合篩選出需要的資料

也就是通過seed與compile的比對,得出最終的結果了

 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的組合原理

這個地方是最繞的,也是最暈的,是以還是要深入的了解才行哦

先上個簡單的流程圖:

畫的不好 哈哈

jQuery 2.0.3 源碼分析Sizzle引擎 - 超級比對

執行分解:

第一步:

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

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

context.getElementsByTagName( input )      

是以找到了input ,因為隻可以用 tag是查詢,但是此時結果是個合集,引入seed的概念,稱之為種子合集

第二步:

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

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

第三步:

通過matcherFromTokens函數,然後根據 關系選擇器 【'>',"空","~","+"】拆分分組,因為DOM中的節點都是存在關系的,是以引入

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

編譯規則: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

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

jQuery 2.0.3 源碼分析Sizzle引擎 - 超級比對

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

但是有一個不同的地方,下一個分組會把上一個分組給一并合并了

是以整個關系就是一個依賴嵌套很深的結構

最終暴露出來的終極比對器其實隻有一個閉包,但是有内嵌很深的分組閉包了

依照從左邊往右依次生成閉包,然後把上一組閉包又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 >的節點
      operator === "*=" ? check && result.indexOf( check ) > -1 :
 
      //如果是末尾相等,判斷目标值是否在目前屬性值的末尾
      operator === "$=" ? check && result.slice( -check.length ) === check :
 
      //這樣解釋: lang~=en 比對這樣 <html >的節點
      operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
 
      //這樣解釋: 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最基本的“編譯原理”應該已經解釋清楚了。

哥們,别光看不頂啊!

轉載于:https://www.cnblogs.com/aaronjs/p/3332805.html

繼續閱讀