Sizzle的編譯過程是調用的Sizzle.compile方法,內部執行了:
matcherFromTokens matcherFromGroupMatchers
把分析關系表,生成用于匹配單個選擇器群組的函數。
matcherFromTokens,它充當了selector“分詞”與Expr中定義的匹配方法的串聯與紐帶的作用,可以說選擇符的各種排列組合都是能適應的了。Sizzle巧妙的就是它沒有直接將拿到的“分詞”結果與Expr中的方法逐個匹配逐個執行,而是先根據規則組合出一個大的匹配方法,最后一步執行。
重點就是:
cached = matcherFromTokens(group[i]);
cached的結果就是matcherFromTokens返回的matchers編譯函數了。
matcherFromTokens的分解是有規律的:
語義節點+關系選擇器的組合
div > p + div.aaron input[type="checkbox"]
Expr.relative 匹配關系選擇器類型,當遇到關系選擇器時elementMatcher函數將matchers數組中的函數生成一個函數。
在遞歸分解tokens中的詞法元素時,提出第一個typ匹配到對應的處理方法:
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);
"TAG": function(nodeNameSelector) { var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); return nodeNameSelecto r === "*" ? function() { return true; } : function(elem) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; },
matcher其實最終結果返回的就是bool值,但是這里返回只是一個閉包函數,不會馬上執行,這個過程換句話就是編譯成一個匿名函數。
繼續往下分解:
如果遇到關系選擇符就會合并分組了
matchers = [addCombinator(elementMatcher(matchers), matcher)];
通過elementMatcher生成一個終極匹配器:
function elementMatcher(matchers) { //生成一個終極匹配器 return matchers.length > 1 ? //如果是多個匹配器的情況,那么就需要elem符合全部匹配器規則 function(elem, context, xml) { var i = matchers.length; //從右到左開始匹配 while (i--) { //如果有一個沒匹配中,那就說明該節點elem不符合規則 if (!matchers[i](elem, context, xml)) { return false; } } return true; } : //單個匹配器的話就返回自己即可 matchers[0]; }
看代碼大概就知道,就是分解這個子匹配器了,返回又一個curry函數,給addCombinator方法:
matcher為當前詞素前的“終極匹配器”,combinator為位置詞素,根據關系選擇器檢查,如果是這類沒有位置詞素的選擇器:“#id.aaron[name="checkbox"]”,從右到左依次看看當前節點elem是否匹配規則即可。
但是由于有了位置詞素,那么判斷的時候就不是簡單判斷當前節點了,可能需要判斷elem的兄弟或者父親節點是否依次符合規則。
這是一個遞歸深搜的過程。
所以matchers又經過一層包裝了,然后用同樣的方式遞歸下去,直接到tokens分解完畢。返回的結果一個根據關系選擇器分組后在組合的嵌套很深的閉包函數了,整個函數編譯的過程,就結束了,其實整個流程總結就干了那么幾件事:
1、在Expr.filter找出每一個選擇器類型對應的處理方法
2、從右邊往左,向父級匹配的時候。注意詞素關系,引入relative記錄這個映射的關系
3、把對應的處理函數壓入matchers數組
整個編譯過程就完成了,其實粗看就是把函數一層一層包裝下去,之后通過匹配器傳入對應的種子合集seed一層一層的解開。
請驗證,完成請求
由于請求次數過多,請先驗證,完成再次請求
打開微信掃碼自動綁定
綁定后可得到
使用 Ctrl+D 可將課程添加到書簽
舉報