jQuery選擇器源碼解讀(五):tokenize的解析過(guò)程
以下分析基于jQuery-1.10.2.js版本。
下面將以$("div:not(.class:contain('span')):eq(3)")為例,說(shuō)明tokenize和preFilter各段代碼是如何協(xié)調(diào)完成解析的。若想了解tokenize方法和preFilter類(lèi)的每行代碼的詳細(xì)解釋?zhuān)?qǐng)參看如下兩篇文章:
http://www.dbjr.com.cn/article/63155.htm
http://www.dbjr.com.cn/article/63163.htm
下面是tokenize方法的源碼,為了簡(jiǎn)便期間,我把有關(guān)緩存、逗號(hào)的匹配以及關(guān)系符的匹配的代碼全部去掉了,只留了與當(dāng)前例子有關(guān)的核心代碼。被去掉的代碼很簡(jiǎn)單,若需要可以看一下上述文章即可。
另外,代碼統(tǒng)一寫(xiě)在說(shuō)明文字上方。
function tokenize(selector, parseOnly) {
var matched, match, tokens, type, soFar, groups, preFilters;
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
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);
}
首先,jQuery執(zhí)行過(guò)程中由select方法首次調(diào)用tokenize,并將"div:not(.class:contain('span')):eq(3)"作為selector參數(shù)傳入該方法。
soFar = selector;
soFar = "div:not(.class:contain('span')):eq(3)"
第一次進(jìn)入while循環(huán)時(shí),由于matched還未被賦值,所以執(zhí)行if內(nèi)的如下語(yǔ)句體,該語(yǔ)句將初始化tokens變量,同時(shí),將tokens壓入groups數(shù)組。
groups.push(tokens = []);
之后,進(jìn)入for語(yǔ)句。
第一次for循環(huán):從Expr.filter中取出第一個(gè)元素"TAG"賦給type變量,執(zhí)行循環(huán)體代碼。
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
match = matchExpr[type].exec(soFar)的執(zhí)行結(jié)果如下:
match =["div", "div"]
示例的第一個(gè)選擇器為div,匹配matchExpr["TAG"]的正則表達(dá)式,且不存在preFilters["TAG"],故執(zhí)行if內(nèi)語(yǔ)句體。
matched = match.shift();
移除match中的第一個(gè)元素div,并將該元素賦予matched變量,此時(shí)matched="div",match = ["div"]
tokens.push({
value : matched,
type : type,
matches : match
}
創(chuàng)建一個(gè)新對(duì)象{ value: "div", type:"TAG", matches: ["div"] },并將該對(duì)象壓入tokens數(shù)組。
soFar = soFar.slice(matched.length);
soFar變量刪除div,此時(shí),soFar=":not(.class:contain('span')):eq(3)"
第二次for循環(huán):從Expr.filter中取出第二個(gè)元素"CLASS"賦給type變量,執(zhí)行循環(huán)體代碼。
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
由于當(dāng)前的soFar=":not(.class:contain('span')):eq(3)",不匹配CLASS類(lèi)型的正則表達(dá)式,故結(jié)束本次循環(huán)。
第三次for循環(huán):從Expr.filter中取出第三個(gè)元素"ATTR"賦給type變量,執(zhí)行循環(huán)體代碼。
同樣,由于當(dāng)前剩余選擇器不是屬性選擇器,故結(jié)束本次循環(huán)。
第四次for循環(huán):從Expr.filter中取出第四個(gè)元素"CHILD"賦給type變量,執(zhí)行循環(huán)體代碼。
同樣,由于當(dāng)前剩余選擇器不是CHILD選擇器,故結(jié)束本次循環(huán)。
第五次for循環(huán):從Expr.filter中取出第五個(gè)元素"PSEUDO"賦給type變量,執(zhí)行循環(huán)體代碼。
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
match = matchExpr[type].exec(soFar)的執(zhí)行結(jié)果如下:
[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
由于存在preFilters["PSEUDO"],故執(zhí)行其后的代碼:
match = preFilters[type](match)
preFilters["PSEUDO"]代碼如下:
"PSEUDO" : function(match) {
var excess, unquoted = !match[5] && match[2];
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參數(shù)等于:
[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", undefined, undefined, undefined, undefined, undefined
unquoted = !match[5] && match[2]
unquoted = ".class:contain('span')):eq(3"
if (matchExpr["CHILD"].test(match[0])) {
return null;
}
match[0] = ":not(.class:contain('span')):eq(3)",不匹配matchExpr["CHILD"]正則表達(dá)式,不執(zhí)行return null語(yǔ)句。
if (match[3] && match[4] !== undefined) {
match[2] = match[4];
}
由于match[3]和match[4]都等于undefined,故執(zhí)行else的語(yǔ)句體。
else if (unquoted
&& rpseudo.test(unquoted)
&& (excess = tokenize(unquoted, true))
&& (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)
此時(shí),unquoted = ".class:contain('span')):eq(3",為真,而且由于unquoted含有:contain('span'),與正則表達(dá)式rpseudo匹配,故rpseudo.test(unquoted)為真,然后再次調(diào)用tokenize對(duì)unquoted再次解析,如下語(yǔ)句:
excess = tokenize(unquoted, true)
此次調(diào)用tokenize函數(shù)時(shí),傳入的selector參數(shù)等于".class:contain('span')):eq(3",parseOnly等于true。函數(shù)體內(nèi)執(zhí)行過(guò)程如下:
soFar = selector;
soFar = ".class:contain('span')):eq(3"
第一次進(jìn)入while循環(huán)時(shí),由于matched還未被賦值,所以執(zhí)行if內(nèi)的如下語(yǔ)句體,該語(yǔ)句將初始化tokens變量,同時(shí),將tokens壓入groups數(shù)組。
groups.push(tokens = []);
之后,進(jìn)入for語(yǔ)句。
第一次for循環(huán):從Expr.filter中取出第一個(gè)元素"TAG"賦給type變量,執(zhí)行循環(huán)體代碼。
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
由于當(dāng)前剩余選擇器不是TAG選擇器,故結(jié)束本次循環(huán)。
第二次for循環(huán):從Expr.filter中取出第二個(gè)元素"CLASS"賦給type變量,執(zhí)行循環(huán)體代碼。
match = matchExpr[type].exec(soFar)的執(zhí)行結(jié)果如下:
match = ["class" , "class"]
由于不存在preFilters["CLASS"],故執(zhí)行if內(nèi)語(yǔ)句體。
matched = match.shift();
移除match中的第一個(gè)元素class,并將該元素賦予matched變量,此時(shí)matched="class",match = ["class"]
tokens.push({
value : matched,
type : type,
matches : match
}
創(chuàng)建一個(gè)新對(duì)象{ value: "class", type:"CLASS", matches: ["class"] },并將該對(duì)象壓入tokens數(shù)組。
soFar = soFar.slice(matched.length);
soFar變量刪除class,此時(shí),soFar = ":contain('span')):eq(3"
第三次for循環(huán):從Expr.filter中取出第三個(gè)元素"ATTR"賦給type變量,執(zhí)行循環(huán)體代碼。
同樣,由于當(dāng)前剩余選擇器不是屬性選擇器,故結(jié)束本次循環(huán)。
第四次for循環(huán):從Expr.filter中取出第四個(gè)元素"CHILD"賦給type變量,執(zhí)行循環(huán)體代碼。
同樣,由于當(dāng)前剩余選擇器不是CHILD選擇器,故結(jié)束本次循環(huán)。
第五次for循環(huán):從Expr.filter中取出第五個(gè)元素"PSEUDO"賦給type變量,執(zhí)行循環(huán)體代碼。
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
match = matchExpr[type].exec(soFar)的執(zhí)行結(jié)果如下:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]
由于存在preFilters["PSEUDO"],故執(zhí)行其后的代碼:
match = preFilters[type](match)
preFilters["PSEUDO"]代碼如上所示,此處不再列舉。
"PSEUDO" : function(match) {
var excess, unquoted = !match[5] && match[2];
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參數(shù)等于:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]
unquoted = !match[5] && match[2];
unquoted = "span"
if (matchExpr["CHILD"].test(match[0])) {
return null;
}
由于":contain('span')"不匹配matchExpr["CHILD"]正則表達(dá)式,故不執(zhí)行內(nèi)部語(yǔ)句體。
if (match[3] && match[4] !== undefined) {
match[2] = match[4];
}
由于match[3] = "'",match[4] ="span",故執(zhí)行if內(nèi)部語(yǔ)句體,將"span"賦予match[2]
return match.slice(0, 3);
返回match前三個(gè)元素的副本
此時(shí)回到tokenize方法的for循環(huán)內(nèi)繼續(xù)執(zhí)行,此時(shí)各變量值如下:
match = [":contain('span')", "contain", "span"]
soFar = ":contain('span')):eq(3"
matched = match.shift();
將":contain('span')"移除match數(shù)組,并賦予matched變量
tokens.push({
value : matched,
type : type,
matches : match
}
創(chuàng)建一個(gè)新對(duì)象{ value:
":contain('span')", type:"PSEUDO", matches: ["contain", "span"] },并將該對(duì)象壓入tokens數(shù)組。
soFar = soFar.slice(matched.length);
soFar變量刪除":contain('span')",此時(shí),soFar="):eq(3)",之后,直至for循環(huán)結(jié)束,且再次執(zhí)行while循環(huán),也沒(méi)有一個(gè)有效選擇器,故退出while循環(huán)。
return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :
tokenCache(selector, groups).slice(0);
由于此時(shí)parseOnly = true,故返回此時(shí)soFar的長(zhǎng)度6,繼續(xù)執(zhí)行preFilters["PSEUDO"]的代碼
else if (unquoted
&& rpseudo.test(unquoted)
&& (excess = tokenize(unquoted, true))
&& (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)
將6賦予excess變量,然后由代碼
excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length
計(jì)算出:not選擇器結(jié)束位置(即右括號(hào)位置)22
match[0] = match[0].slice(0, excess);
match[2] = unquoted.slice(0, excess);
分別計(jì)算出完整的:not選擇器字符串(match[0])和其括號(hào)內(nèi)的字符串(match[2]),分別等于:
match[0] = ":not(.class:contain('span'))"
match[2] = ".class:contain('span')"
return match.slice(0, 3);
返回match中前三個(gè)元素的副本。
回到tokenize函數(shù),此時(shí)match = [":not(.class:contain('span'))", "not", ".class:contain('span')"]
matched = match.shift();
移除match中的第一個(gè)元素":not(.class:contain('span'))",并將該元素賦予matched變量,此時(shí)matched="":not(.class:contain('span'))"",
match = ["not", ".class:contain('span')"]
tokens.push({
value : matched,
type : type,
matches : match
}
創(chuàng)建一個(gè)新對(duì)象{ value: ":not(.class:contain('span'))"", type:"PSEUDO", matches: ["not", ".class:contain('span')"] },并將該對(duì)象壓入tokens數(shù)組。此時(shí)tokens共有兩個(gè)元素分別是div和not選擇器。
soFar = soFar.slice(matched.length);
soFar變量刪除":not(.class:contain('span'))",此時(shí),soFar=":eq(3)",結(jié)束本次for循環(huán)后,再次回到while循環(huán),同樣方式,獲取tokens的第三個(gè)元素eq選擇器,過(guò)程與not一致,這里就不再細(xì)講了。最后的groups的結(jié)果如下:
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"] }
return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :
tokenCache(selector, groups).slice(0);
由于parseOnly = undefined,所以執(zhí)行tokenCache(selector, groups).slice(0),該語(yǔ)句將groups壓入緩存,并返回其副本。
由此,完成了所有的解析,或許有人會(huì)問(wèn),這里第二個(gè)元素并沒(méi)有解析出來(lái)呀,是的,這個(gè)需要在實(shí)際運(yùn)行中再次解析。當(dāng)然,這里若可以將剛才解析."class:contain('span')):eq(3"時(shí),將有效選擇器的結(jié)果保存到緩存內(nèi),那么就可以避免再次解析,提高執(zhí)行速度。但這也僅僅提高了當(dāng)前這次運(yùn)行速度。因?yàn)樵趫?zhí)行過(guò)程中,對(duì)".class:contain('span')"再次提交解析時(shí),會(huì)存入緩存。
至此,整個(gè)執(zhí)行過(guò)程已經(jīng)全部結(jié)束。
- jQuery選擇器源碼解讀(二):select方法
- jQuery選擇器源碼解讀(三):tokenize方法
- jQuery選擇器源碼解讀(六):Sizzle選擇器匹配邏輯分析
- jQuery選擇器源碼解讀(七):elementMatcher函數(shù)
- jquery表單對(duì)象屬性過(guò)濾選擇器實(shí)例分析
- JQuery中屬性過(guò)濾選擇器用法實(shí)例分析
- JQuery中基礎(chǔ)過(guò)濾選擇器用法實(shí)例分析
- Jquery中基本選擇器用法實(shí)例詳解
- JQuery中層次選擇器用法實(shí)例詳解
- JQuery選擇器、過(guò)濾器大整理
- jquery實(shí)現(xiàn)不包含當(dāng)前項(xiàng)的選擇器實(shí)例
- 使用jQuery在對(duì)象中緩存選擇器的簡(jiǎn)單方法
- jquery選擇器簡(jiǎn)述
相關(guān)文章
jQuery實(shí)現(xiàn)簡(jiǎn)單彈出框效果實(shí)例
這篇文章主要給大家介紹了關(guān)于jQuery實(shí)現(xiàn)簡(jiǎn)單彈出框效果的相關(guān)資料,幾天一直在研究JQuery,確實(shí)很好用,有很多需求都是要彈出框,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06jquery xMarquee實(shí)現(xiàn)文字水平無(wú)縫滾動(dòng)效果
這篇文章主要介紹了jquery xMarquee實(shí)現(xiàn)文字水平無(wú)縫滾動(dòng)效果,需要的朋友可以參考下2014-04-04jQuery實(shí)現(xiàn)購(gòu)物車(chē)多物品數(shù)量的加減+總價(jià)計(jì)算
這篇文章主要介紹了jQuery實(shí)現(xiàn)購(gòu)物車(chē)多物品數(shù)量的加減+總價(jià)計(jì)算,需要的朋友可以參考下2014-06-06jQuery DOM節(jié)點(diǎn)的遍歷方法小結(jié)
本篇文章主要介紹了jQuery DOM節(jié)點(diǎn)的遍歷方法小結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08jQuery實(shí)現(xiàn)的鼠標(biāo)經(jīng)過(guò)時(shí)變寬的效果(附demo源碼)
這篇文章主要介紹了jQuery實(shí)現(xiàn)的鼠標(biāo)經(jīng)過(guò)時(shí)變寬的效果,實(shí)例演示了jQuery的Kwicks插件實(shí)現(xiàn)針對(duì)鼠標(biāo)事件的響應(yīng)與頁(yè)面元素樣式動(dòng)態(tài)變換的相關(guān)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-04-04高效Web開(kāi)發(fā)的10個(gè)jQuery代碼片段
這篇文章主要為大家詳細(xì)介紹了高效Web開(kāi)發(fā)的10個(gè)jQuery代碼片段,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07jquery簡(jiǎn)單插件制作(fn.extend)完整實(shí)例
這篇文章主要介紹了jquery簡(jiǎn)單插件制作(fn.extend)方法,結(jié)合完整實(shí)例形式分析了jQuery fn.extend擴(kuò)展插件的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-05-05