<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詞法分析器分組
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的關系是一個層級的包含結構,然后依次這樣遞歸
這個地方相當繞!!!!
生成的最后
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節點,這樣就能確定最終的元素跟這個元素是有關系的
然后把剩余的選擇器單元開始生成一個匹配器,主要是用來做篩選,最后根據關系分組
如果就依次匹配往上查找,通過關系處理器定位上一個節點的元素,通過普通匹配器去確定是否為可選的內容