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

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

jQuery引用方法詳解:快速上手指南jQuery是一個受歡迎的JavaScript庫,被廣泛用於網站開發中,它簡化了JavaScript編程,並為開發者提供了豐富的功能和特性。本文將詳細介紹jQuery的引用方法,並提供具體的程式碼範例,幫助讀者快速上手。引入jQuery首先,我們需要在HTML檔案中引入jQuery函式庫。可以透過CDN連結的方式引入,也可以下載

jQuery中如何使用PUT請求方式?在jQuery中,發送PUT請求的方法與發送其他類型的請求類似,但需要注意一些細節和參數設定。 PUT請求通常用於更新資源,例如更新資料庫中的資料或更新伺服器上的檔案。以下是在jQuery中使用PUT請求方式的具體程式碼範例。首先,確保引入了jQuery庫文件,然後可以透過以下方式發送PUT請求:$.ajax({u

標題:jQuery小技巧:快速修改頁面所有a標籤的文字在網頁開發中,我們經常需要對頁面中的元素進行修改和操作。使用jQuery時,有時候需要一次修改頁面中所有a標籤的文字內容,這樣可以節省時間和精力。以下將介紹如何使用jQuery快速修改頁面所有a標籤的文本,同時給出具體的程式碼範例。首先,我們需要引入jQuery庫文件,確保在頁面中引入了以下程式碼:<

jQuery如何移除元素的height屬性?在前端開發中,經常會遇到需要操作元素的高度屬性的需求。有時候,我們可能需要動態改變元素的高度,而有時候又需要移除元素的高度屬性。本文將介紹如何使用jQuery來移除元素的高度屬性,並提供具體的程式碼範例。在使用jQuery操作高度屬性之前,我們首先需要了解CSS中的height屬性。 height屬性用於設定元素的高度

標題:使用jQuery修改所有a標籤的文字內容jQuery是一款受歡迎的JavaScript庫,被廣泛用於處理DOM操作。在網頁開發中,經常會遇到需要修改頁面上連結標籤(a標籤)的文字內容的需求。本文將介紹如何使用jQuery來實現這個目標,並提供具體的程式碼範例。首先,我們需要在頁面中引入jQuery庫。在HTML檔案中加入以下程式碼:

jQuery是一種流行的JavaScript庫,被廣泛用於處理網頁中的DOM操作和事件處理。在jQuery中,eq()方法是用來選擇指定索引位置的元素的方法,具體使用方法和應用場景如下。在jQuery中,eq()方法選擇指定索引位置的元素。索引位置從0開始計數,即第一個元素的索引是0,第二個元素的索引是1,依此類推。 eq()方法的語法如下:$("s

如何判斷jQuery元素是否具有特定屬性?在使用jQuery操作DOM元素時,常會遇到需要判斷元素是否具有某個特定屬性的情況。在這種情況下,我們可以藉助jQuery提供的方法來輕鬆實現這項功能。以下將介紹兩種常用的方法來判斷一個jQuery元素是否具有特定屬性,並附上具體的程式碼範例。方法一:使用attr()方法和typeof運算子//判斷元素是否具有特定屬

jQuery是一個受歡迎的JavaScript函式庫,廣泛用於網頁開發。在網頁開發過程中,經常需要透過JavaScript動態地在表格中新增一行。本文將介紹如何使用jQuery為表格新增一行,並提供具體的程式碼範例。首先,我們需要在HTML頁面中引入jQuery函式庫。可以透過以下程式碼在標籤中引入jQuery庫:
