天天看點

jQuery選擇器代碼詳解(五)——執行個體說明tokenize的解析過程

原創文章,轉載請寫明出處,多謝!

以下分析基于jQuery-1.10.2.js版本。

下面将以$("div:not(.class:contain('span')):eq(3)")為例,說明tokenize和preFilter各段代碼是如何協調完成解析的。若想了解tokenize方法和preFilter類的每行代碼的詳細解釋,請參看如下兩篇文章:

jQuery選擇器代碼詳解(三)——tokenize方法

jQuery選擇器代碼詳解(四)——Expr.preFilter

下面是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, 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"時,将有效選擇器的結果儲存到緩存内,那麼就可以避免再次解析,提高執行速度。但這也僅僅提高了目前這次運作速度。因為在執行過程中,對".class:contain('span')"再次送出解析時,會存入緩存。

至此,整個執行過程已經全部結束。

各位朋友,若覺得寫得不錯,幫我頂一下,給點動力,多謝!

jQuery選擇器代碼詳解(一)——Sizzle方法

jQuery選擇器代碼詳解(二)——select方法

jQuery選擇器代碼詳解(三)——tokenize方法

jQuery選擇器代碼詳解(四)——Expr.preFilter

jQuery選擇器代碼詳解(五)——執行個體說明tokenize的解析過程

jQuery選擇器代碼詳解(六)——Sizzle選擇器比對邏輯分析

jQuery選擇器代碼詳解(七)——elementMatcher函數

jQuery選擇器代碼詳解(八)——addCombinator函數

繼續閱讀