1. 程式人生 > >sizzle分析記錄:分解流程

sizzle分析記錄:分解流程

<form>
  <label>Name:</label>
  <input name="name" />
  <fieldset>
      <label>Newsletter:</label>
      <div name="newsletter" /><p>1<p</div>
      <div name="letter" /><p name='aaron'>2<p></div>
      <div name="tter" /><p>3<p</div>
 </fieldset>
</form>

js

$("form div > p[name=aaron]")

解析的流程:

編譯器:分5個步驟

涉及: TAG元素 關係選擇器 屬性選擇器

1:通過tokenize詞法分析器分組

image

2:遍歷tokens,從右邊往左邊開始篩選,最快定位到目標元素合集

//先看看有沒有搜尋器find,搜尋器就是瀏覽器一些原生的取DOM介面,簡單的表述就是以下物件了
            // Expr.find = {
            // 'ID'    : context.getElementById,
            // 'CLASS' : context.getElementsByClassName,
// 'TAG' : context.getElementsByTagName // }

操作如下

Expr.find["TAG"] = support.getElementsByTagName ?
    function( tag, context ) {
        if ( typeof context.getElementsByTagName !== strundefined ) {
            return context.getElementsByTagName( tag );
        }
    } :

那麼第一篩選找到的定位元素,就形成了一個 seed種子合集,那麼餘下的所有的操作都是圍繞這個種子合集處理

因為節點總是存在各種關係的,所以不管是通過這個最靠近的目標的元素,往上還是往下 都是可以處理的

3:重組選擇器,開始執行繼續分解"form div > [name=aaron]"

因為種子合已經抽出了,所以選擇器就需要重新排列

"form div > [name=aaron]"

踢掉了P元素,已經被抽離了

4 : 生成編譯處理器

這裡為什麼要這麼複雜,因為生成了編譯閉包可以快取起來,通過這種機制,增加了重複選擇器的效率

在matcherFromTokens方法中通過分解tokens生成對應的處理器

例如:form div [name=aaron]

在分解過程中分2大塊

A:關係選擇器的處理  > + ~ 空

B: ATTR CHILD CLASS ID PSEUDO TAG的處理

用matchers保留組合關係

1:分解第一個TAG:form 儲存處理器到matchers.push( Expr.filter[“TAG”]) ;

2:分解第二個“空”的關係選擇器,此時

 A:用elementMatcher把之前的matchers壓入到這個匹配裡面,生成一個遍歷方法的處理

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

B:用addCombinator再次包裝,生成一個位置關係的查詢關係

function addCombinator( matcher, combinator, base ) {
    var dir = combinator.dir,
        checkNonElements = base && dir === "parentNode",
        doneName = done++;

    return 
        // Check against all ancestor/preceding elements
        // 檢查所有祖先/元素
        function( elem, context, xml ) {
            var oldCache, outerCache,
                newCache = [ dirruns, doneName ];
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        outerCache = elem[ expando ] || (elem[ expando ] = {});
                        if ( (oldCache = outerCache[ dir ]) &&
                            oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

                            // Assign to newCache so results back-propagate to previous elements
                            return (newCache[ 2 ] = oldCache[ 2 ]);
                        } else {
                            // Reuse newcache so results back-propagate to previous elements
                            outerCache[ dir ] = newCache;

                            // A match means we're done; a fail means we have to keep checking
                            if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
                                return true;
                            }
                        }
                    }
                }
        };
}

所以此時的matchers的關係是一個層級的包含結構,然後依次這樣遞迴

image

這個地方相當繞!!!!

生成的最後

cached = matcherFromTokens( match[i] );

變成了一個超大的巢狀閉包

5: 通過matcherFromGroupMatchers這個函式來生成最終的匹配器

var bySet = setMatchers.length > 0,
        byElement = elementMatchers.length > 0,

        superMatcher = function(seed, context, xml, results, outermost) {
            //分解這個匹配處理器
        }


    return superMatcher

通過matcherFromGroupMatchers的處理最直接的就是能看出,elementMatchers, setMatchers 2個結果不需要再返回出去,直接形成curry的方法,在內部就合併引數

外面就直接呼叫了,這樣

var compileFunc = compiled || compile( selector, match );

compileFunc(
    seed,
    context,
    !documentIsHTML,
    results,
    outermost
);

compileFunc 一直是持有elementMatchers, setMatchers 的引用的,這個設計的手法還是值得借鑑的

執行期:

至此之前的5個步驟都是編譯成函式處理器的過程,然後就是開始執行了

粗的原理就是把直接分解出來的seed種子合集丟到這個處理器中,然後處理器就會根據各種關係進行分解匹配

從而得到結果集

superMatcher:

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

抽出第一個seed元素,p

然後把p丟到atrr是過濾篩選器中去匹配下,看看是否能找到對應的這個屬性

當然還是繼續從右往左邊匹配過濾了

一次是【name=aaron】 => div => from

matchers[i] => Expr.filter.ATTR =>

p.getAttribute(‘name=aaron’) => 得到結果

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

如果匹配失敗,自然就退出了  return false ,就不需要在往前找了 ,然後再次遞迴seed

如果成功,就需要再深入的匹配了

因為是從右到左逐個匹配,所以往前走就會遇到關係選擇器的問題,

那麼jQuery把四種關係 > + ~ 空的處理給抽出一個具體的方法就是addCombinator

1 "form div > p[name=aaron]"
2 seed => p
3 篩選[name=aaron]
4 > => addCombinator方法 找到對應關係對映的父節點elem
5 elem中去匹配div 遞迴elementMatcher方法
6 “空” =>  addCombinator方法找到祖先父節點elem
7 elem中去找form為止
可見這個查詢是及其複雜繁瑣的
總結:

sizzle對選擇器的大概是思路:

分解所有的選擇器為最小單元,從右往左邊開始挑出一個瀏覽器的API能快速定位的元素TAG,ID,CLASS節點,這樣就能確定最終的元素跟這個元素是有關係的

然後把剩餘的選擇器單元開始生成一個匹配器,主要是用來做篩選,最後根據關係分組

如果就依次匹配往上查詢,通過關係處理器定位上一個節點的元素,通過普通匹配器去確定是否為可選的內容