聲明:本文爲原創文章,如需轉載,請註明來源並保留原文連接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最基本的「編譯原理」應該已經解釋清楚了。
哥們,別光看不頂啊!