以下分析基于jQuery-1.10.2.js版本。
下面將以$("div:not(.class:contain('span')):eq(3)")為例,說明tokenize和preFilter各段代碼是如何協調完成解析的。若想了解tokenize方法和preFilter類的每行代碼的詳細解釋,請參看如下兩篇文章:
//www.49028c.com/article/63155.htm
//www.49028c.com/article/63163.htm
下面是tokenize方法的源碼,為了簡便期間,我把有關緩存、逗號的匹配以及關系符的匹配的代碼全部去掉了,只留了與當前例子有關的核心代碼。被去掉的代碼很簡單,若需要可以看一下上述文章即可。
另外,代碼統一寫在說明文字上方。
while (soFar) {
if (!matched) {
groups.push(tokens = []);
}
matched = false;
for (type in Expr.filter) {
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
matched = match.shift();
tokens.push({
value : matched,
type : type,
matches : match
});
soFar = soFar.slice(matched.length);
}
}
if (!matched) {
break;
}
}
return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :
tokenCache(selector, groups).slice(0);
}
soFar = "div:not(.class:contain('span')):eq(3)"
第一次進入while循環時,由于matched還未被賦值,所以執行if內的如下語句體,該語句將初始化tokens變量,同時,將tokens壓入groups數組。
第一次for循環:從Expr.filter中取出第一個元素"TAG"賦給type變量,執行循環體代碼。
match = matchExpr[type].exec(soFar)的執行結果如下:
match =["div", "div"]
示例的第一個選擇器為div,匹配matchExpr["TAG"]的正則表達式,且不存在preFilters["TAG"],故執行if內語句體。
移除match中的第一個元素div,并將該元素賦予matched變量,此時matched="div",match = ["div"]
創建一個新對象{ value: "div", type:"TAG", matches: ["div"] },并將該對象壓入tokens數組。
soFar變量刪除div,此時,soFar=":not(.class:contain('span')):eq(3)"
第二次for循環:從Expr.filter中取出第二個元素"CLASS"賦給type變量,執行循環體代碼。
由于當前的soFar=":not(.class:contain('span')):eq(3)",不匹配CLASS類型的正則表達式,故結束本次循環。
第三次for循環:從Expr.filter中取出第三個元素"ATTR"賦給type變量,執行循環體代碼。
同樣,由于當前剩余選擇器不是屬性選擇器,故結束本次循環。
第四次for循環:從Expr.filter中取出第四個元素"CHILD"賦給type變量,執行循環體代碼。
同樣,由于當前剩余選擇器不是CHILD選擇器,故結束本次循環。
第五次for循環:從Expr.filter中取出第五個元素"PSEUDO"賦給type變量,執行循環體代碼。
match = matchExpr[type].exec(soFar)的執行結果如下:
[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
由于存在preFilters["PSEUDO"],故執行其后的代碼:
preFilters["PSEUDO"]代碼如下:
if (matchExpr["CHILD"].test(match[0])) {
return null;
}
if (match[3] && match[4] !== undefined) {
match[2] = match[4];
} else if (unquoted
&& rpseudo.test(unquoted)
&& (excess = tokenize(unquoted, true))
&& (excess = unquoted.indexOf(")", unquoted.length
- excess)
- unquoted.length)) {
match[0] = match[0].slice(0, excess);
match[2] = unquoted.slice(0, excess);
}
return match.slice(0, 3);
}
傳入的match參數等于:
unquoted = ".class:contain('span')):eq(3"
match[0] = ":not(.class:contain('span')):eq(3)",不匹配matchExpr["CHILD"]正則表達式,不執行return null語句。
由于match[3]和match[4]都等于undefined,故執行else的語句體。
此時,unquoted = ".class:contain('span')):eq(3",為真,而且由于unquoted含有:contain('span'),與正則表達式rpseudo匹配,故rpseudo.test(unquoted)為真,然后再次調用tokenize對unquoted再次解析,如下語句:
此次調用tokenize函數時,傳入的selector參數等于".class:contain('span')):eq(3",parseOnly等于true。函數體內執行過程如下:
soFar = ".class:contain('span')):eq(3"
第一次進入while循環時,由于matched還未被賦值,所以執行if內的如下語句體,該語句將初始化tokens變量,同時,將tokens壓入groups數組。
之后,進入for語句。
第一次for循環:從Expr.filter中取出第一個元素"TAG"賦給type變量,執行循環體代碼。
由于當前剩余選擇器不是TAG選擇器,故結束本次循環。
第二次for循環:從Expr.filter中取出第二個元素"CLASS"賦給type變量,執行循環體代碼。
match = matchExpr[type].exec(soFar)的執行結果如下:
match = ["class" , "class"]
由于不存在preFilters["CLASS"],故執行if內語句體。
移除match中的第一個元素class,并將該元素賦予matched變量,此時matched="class",match = ["class"]
創建一個新對象{ value: "class", type:"CLASS", matches: ["class"] },并將該對象壓入tokens數組。
soFar變量刪除class,此時,soFar = ":contain('span')):eq(3"
第三次for循環:從Expr.filter中取出第三個元素"ATTR"賦給type變量,執行循環體代碼。
同樣,由于當前剩余選擇器不是屬性選擇器,故結束本次循環。
第四次for循環:從Expr.filter中取出第四個元素"CHILD"賦給type變量,執行循環體代碼。
同樣,由于當前剩余選擇器不是CHILD選擇器,故結束本次循環。
第五次for循環:從Expr.filter中取出第五個元素"PSEUDO"賦給type變量,執行循環體代碼。
match = matchExpr[type].exec(soFar)的執行結果如下:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]
由于存在preFilters["PSEUDO"],故執行其后的代碼:
preFilters["PSEUDO"]代碼如上所示,此處不再列舉。
傳入的match參數等于:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]
unquoted = "span"
由于":contain('span')"不匹配matchExpr["CHILD"]正則表達式,故不執行內部語句體。
由于match[3] = "'",match[4] ="span",故執行if內部語句體,將"span"賦予match[2]
返回match前三個元素的副本
此時回到tokenize方法的for循環內繼續執行,此時各變量值如下:
match = [":contain('span')", "contain", "span"]
soFar = ":contain('span')):eq(3"
將":contain('span')"移除match數組,并賦予matched變量
創建一個新對象{ value:
":contain('span')", type:"PSEUDO", matches: ["contain", "span"] },并將該對象壓入tokens數組。
soFar變量刪除":contain('span')",此時,soFar="):eq(3)",之后,直至for循環結束,且再次執行while循環,也沒有一個有效選擇器,故退出while循環。
由于此時parseOnly = true,故返回此時soFar的長度6,繼續執行preFilters["PSEUDO"]的代碼
將6賦予excess變量,然后由代碼
計算出:not選擇器結束位置(即右括號位置)22
分別計算出完整的:not選擇器字符串(match[0])和其括號內的字符串(match[2]),分別等于:
match[0] = ":not(.class:contain('span'))"
match[2] = ".class:contain('span')"
返回match中前三個元素的副本。
回到tokenize函數,此時match = [":not(.class:contain('span'))", "not", ".class:contain('span')"]
移除match中的第一個元素":not(.class:contain('span'))",并將該元素賦予matched變量,此時matched="":not(.class:contain('span'))"",
match = ["not", ".class:contain('span')"]
創建一個新對象{ value: ":not(.class:contain('span'))"", type:"PSEUDO", matches: ["not", ".class:contain('span')"] },并將該對象壓入tokens數組。此時tokens共有兩個元素分別是div和not選擇器。
soFar變量刪除":not(.class:contain('span'))",此時,soFar=":eq(3)",結束本次for循環后,再次回到while循環,同樣方式,獲取tokens的第三個元素eq選擇器,過程與not一致,這里就不再細講了。最后的groups的結果如下:
group[0][0] = {value: "div", type: "TAG", matches: ["div"] }
group[0][1] = {value: ":not(.class:contain('span'))", type: "PSEUDO", matches: ["not", ".class:contain('span')"] }
group[0][2] = {value: ":eq(3)", type: "PSEUDO", matches: ["eq", "3"] }
由于parseOnly = undefined,所以執行tokenCache(selector, groups).slice(0),該語句將groups壓入緩存,并返回其副本。
由此,完成了所有的解析,或許有人會問,這里第二個元素并沒有解析出來呀,是的,這個需要在實際運行中再次解析。當然,這里若可以將剛才解析."class:contain('span')):eq(3"時,將有效選擇器的結果保存到緩存內,那么就可以避免再次解析,提高執行速度。但這也僅僅提高了當前這次運行速度。因為在執行過程中,對".class:contain('span')"再次提交解析時,會存入緩存。
至此,整個執行過程已經全部結束。
新聞熱點
疑難解答