聲明:本文為原創文章,如需轉載,請注明來源并保留原文連結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的組合原理
這個地方是最繞的,也是最暈的,是以還是要深入的了解才行哦
先上個簡單的流程圖:
畫的不好 哈哈
執行分解:
第一步:
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
三個方法嵌套處理出來的結構
然後繼續分解下一組,遇到關系選擇器又繼續依照以上的過程分解
但是有一個不同的地方,下一個分組會把上一個分組給一并合并了
是以整個關系就是一個依賴嵌套很深的結構
最終暴露出來的終極比對器其實隻有一個閉包,但是有内嵌很深的分組閉包了
依照從左邊往右依次生成閉包,然後把上一組閉包又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