This article mainly introduces the jQuery selector source code explanation (5): The tokenize parsing process. This article uses a detailed comment to explain the parsing process of the tokenize method, need a friend can refer to the following analysis based on jQuery-1.10.2.js version.
$ ("P: not (. class: contain ('span '): eq (3) ") is used as an example to explain how tokenize and preFilter complete parsing. For more information about each line of code in the tokenize method and preFilter class, see the following two articles:
Http://www.jb51.net/article/63155.htm
Http://www.jb51.net/article/63163.htm
The following is the source code of the tokenize method. For the sake of simplicity, I have removed all the Code related to cache, comma matching, and link character matching, only the core Code related to the current example is left. The code to be removed is very simple. You can read the above article if necessary.
In addition, the Code is written at the top of the explanatory text.
The Code is as follows:
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 = matchexpr1_type.exe c (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 );
}
First, tokenize is called by the select method for the first time during jQuery execution, and "p: not (. class: contain ('span '): eq (3) "is passed in as the selector parameter.
The Code is as follows:
SoFar = selector;
SoFar = "p: not (. class: contain ('span '): eq (3 )"
When you enter the while loop for the first time, because matched has not been assigned a value, execute the following statement body in the if statement. This statement will initialize the tokens variable and press tokens into the groups array.
The Code is as follows:
Groups. push (tokens = []);
Then, enter the for statement.
First for loop: extract the first element "TAG" from Expr. filter and assign it to the type variable to execute the loop body code.
The Code is as follows:
If (match = matchexpr1_type.exe c (soFar ))
&&(! PreFilters [type] | (match = preFilters [type]
(Match )))){
The execution result of match = matchexpr1_type.exe c (soFar) is as follows:
Match = ["p", "p"]
The first selector in the example is p, which matches the Regular Expression of matchExpr ["TAG"] and does not exist in preFilters ["TAG"]. Therefore, the if statement body is executed.
The Code is as follows:
Matched = match. shift ();
Remove the first element p in match and assign it to the matched variable. In this case, matched = "p", match = ["p"]
The Code is as follows:
Tokens. push ({
Value: matched,
Type: type,
Matches: match
}
Create a new object {value: "p", type: "TAG", matches: ["p"]} and press the object into the tokens array.
The Code is as follows:
SoFar = soFar. slice (matched. length );
When the soFar variable deletes p, soFar = ": not (. class: contain ('span '): eq (3 )"
The second for loop: extract the second element "CLASS" from Expr. filter and assign it to the type variable to execute the loop body code.
The Code is as follows:
If (match = matchexpr1_type.exe c (soFar ))
&&(! PreFilters [type] | (match = preFilters [type]
(Match )))){
This loop ends because the current soFar = ": not (. class: contain ('span '): eq (3)" does not match the regular expression of the CLASS type.
Third for loop: extract the third element "ATTR" from Expr. filter and assign it to the type variable to execute the loop body code.
Similarly, because the current residual selector is not a property selector, this cycle ends.
The fourth for loop: extract the fourth element "CHILD" from Expr. filter and assign it to the type variable to execute the loop body code.
Similarly, the cycle ends because the current residual selector is not a CHILD selector.
The fifth for loop: extract the fifth element "PSEUDO" from Expr. filter and assign it to the type variable to execute the loop body code.
The Code is as follows:
If (match = matchexpr1_type.exe c (soFar ))
&&(! PreFilters [type] | (match = preFilters [type]
(Match )))){
The execution result of match = matchexpr1_type.exe c (soFar) is as follows:
[": Not (. class: contain ('span '): eq (3) "," not ",". class: contain ('span '): eq (3 ", undefined]
Because preFilters ["PSEUDO"] exists, execute the following code:
The Code is as follows:
Match = preFilters [type] (match)
The preFilters ["PSEUDO"] Code is as follows:
The Code is as follows:
"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 );
}
The input match parameter is equal:
The Code is as follows:
[": Not (. class: contain ('span '): eq (3) "," not ",". class: contain ('span '): eq (3 ", undefined
The Code is as follows:
Unquoted =! Match [5] & match [2]
Unquoted = ". class: contain ('span '): eq (3"
The Code is as follows:
If (matchExpr ["CHILD"]. test (match [0]) {
Return null;
}
Match [0] = ": not (. class: contain ('span '): eq (3) ", does not match the matchExpr [" CHILD "] Regular Expression, and does not execute the return null statement.
The Code is as follows:
If (match [3] & match [4]! = Undefined ){
Match [2] = match [4];
}
Because match [3] and match [4] are both equal to undefined, the statement body that executes else.
The Code is as follows:
Else if (unquoted
& Rpseudo. test (unquoted)
& (Excess = tokenize (unquoted, true ))
& (Excess = unquoted. indexOf (")", unquoted. length-excess)-unquoted. length)
At this time, unquoted = ". class: contain ('span '): eq (3 ", true, and because unquoted contains: contain ('span'), it matches the regular expression rpseudo, so rpseudo. test (unquoted) is true, and then call tokenize to parse unquoted again, the following statement:
The Code is as follows:
Excess = tokenize (unquoted, true)
When the tokenize function is called this time, the input selector parameter is equal to ". class: contain ('span '): eq (3", parseOnly is equal to true. The Execution Process in the function body is as follows:
The Code is as follows:
SoFar = selector;
SoFar = ". class: contain ('span '): eq (3"
When you enter the while loop for the first time, because matched has not been assigned a value, execute the following statement body in the if statement. This statement will initialize the tokens variable and press tokens into the groups array.
The Code is as follows:
Groups. push (tokens = []);
Then, enter the for statement.
First for loop: extract the first element "TAG" from Expr. filter and assign it to the type variable to execute the loop body code.
The Code is as follows:
If (match = matchexpr1_type.exe c (soFar ))
&&(! PreFilters [type] | (match = preFilters [type]
(Match )))){
Because the current residual selector is not a TAG selector, this cycle ends.
The second for loop: extract the second element "CLASS" from Expr. filter and assign it to the type variable to execute the loop body code.
The execution result of match = matchexpr1_type.exe c (soFar) is as follows:
Match = ["class", "class"]
Because preFilters ["CLASS"] does not exist, execute the if statement body.
The Code is as follows:
Matched = match. shift ();
Remove the first element class in match and assign it to the matched variable. In this case, matched = "class", match = ["class"]
The Code is as follows:
Tokens. push ({
Value: matched,
Type: type,
Matches: match
}
Create a new object {value: "class", type: "CLASS", matches: ["class"]} and press the object into the tokens array.
The Code is as follows:
SoFar = soFar. slice (matched. length );
When the soFar variable deletes the class, soFar = ": contain ('span '): eq (3"
Third for loop: extract the third element "ATTR" from Expr. filter and assign it to the type variable to execute the loop body code.
Similarly, because the current residual selector is not a property selector, this cycle ends.
The fourth for loop: extract the fourth element "CHILD" from Expr. filter and assign it to the type variable to execute the loop body code.
Similarly, the cycle ends because the current residual selector is not a CHILD selector.
The fifth for loop: extract the fifth element "PSEUDO" from Expr. filter and assign it to the type variable to execute the loop body code.
The Code is as follows:
If (match = matchexpr1_type.exe c (soFar ))
&&(! PreFilters [type] | (match = preFilters [type]
(Match )))){
The execution result of match = matchexpr1_type.exe c (soFar) is as follows:
[": Contain ('span ')", "contain", "'span", "'", "span", undefined, undefined]
Because preFilters ["PSEUDO"] exists, execute the following code:
The Code is as follows:
Match = preFilters [type] (match)
The preFilters ["PSEUDO"] Code is shown above, which is not listed here.
The Code is as follows:
"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 );
}
The input match parameter is equal:
[": Contain ('span ')", "contain", "'span", "'", "span", undefined, undefined]
The Code is as follows:
Unquoted =! Match [5] & match [2];
Unquoted = "span"
The Code is as follows:
If (matchExpr ["CHILD"]. test (match [0]) {
Return null;
}
Because ": contain ('span ')" does not match the matchExpr ["CHILD"] Regular Expression, internal statement bodies are not executed.
The Code is as follows:
If (match [3] & match [4]! = Undefined ){
Match [2] = match [4];
}
Because match [3] = "'", match [4] = "span", execute the if internal statement body and assign "span" to match [2].
The Code is as follows:
Return match. slice (0, 3 );
Returns a copy of the first three elements of a match.
Return to the for loop of the tokenize method and continue execution. The variable values are as follows:
Match = [": contain ('span ')", "contain", "span"]
SoFar = ": contain ('span '): eq (3"
The Code is as follows:
Matched = match. shift ();
Remove the match array from ": contain ('span ')" and assign the matched variable
The Code is as follows:
Tokens. push ({
Value: matched,
Type: type,
Matches: match
}
Create a new object {value:
": Contain ('span ')", type: "PSEUDO", matches: ["contain", "span"]}, and press this object into the tokens array.
The Code is as follows:
SoFar = soFar. slice (matched. length );
SoFar variable deletes ": contain ('span ')". At this time, soFar = "): eq (3)", and then runs the while loop again until the for loop ends, there is no valid selector, so the while loop is exited.
The Code is as follows:
Return parseOnly? SoFar. length: soFar? Sizzle. error (selector ):
TokenCache (selector, groups). slice (0 );
Because parseOnly = true at this time, return the soFar length of 6 at this time, continue to execute the preFilters ["PSEUDO"] Code
The Code is as follows:
Else if (unquoted
& Rpseudo. test (unquoted)
& (Excess = tokenize (unquoted, true ))
& (Excess = unquoted. indexOf (")", unquoted. length-excess)-unquoted. length)
Assign 6 to the excess variable, and then the code
The Code is as follows:
Excess = unquoted. indexOf (")", unquoted. length-excess)-unquoted. length
Calculated: not selector end position (that is, the right bracket position) 22
The Code is as follows:
Match [0] = match [0]. slice (0, excess );
Match [2] = unquoted. slice (0, excess );
Calculate the complete: not selector string (match [0]) and the string in the brackets (match [2]), which are equal:
Match [0] = ": not (. class: contain ('span '))"
Match [2] = ". class: contain ('span ')"
The Code is as follows:
Return match. slice (0, 3 );
Returns a copy of the first three elements in the match statement.
Return to the tokenize function, and match = [": not (. class: contain ('span ')", "not", ". class: contain ('span')"]
The Code is as follows:
Matched = match. shift ();
Remove the first element in match ": not (. class: contain ('span ') ", and assign this element to the matched variable. In this case, matched =" ": not (. class: contain ('span '))"",
Match = ["not", ". class: contain ('span ')"]
The Code is as follows:
Tokens. push ({
Value: matched,
Type: type,
Matches: match
}
Create a new object {value: ": not (. class: contain ('span ') "", type: "PSEUDO", matches: ["not ",". class: contain ('span ') "]}, and press the object into the tokens array. In this case, tokens has two elements: p and not selector.
The Code is as follows:
SoFar = soFar. slice (matched. length );
SoFar variable deletion ": not (. class: contain ('span ') ", at this time, soFar =": eq (3) ", after the for loop ends, return to the while loop again, the same way, obtain the eq selector, the third element of tokens. The process is the same as that of not. The final groups result is as follows:
Group [0] [0] = {value: "p", type: "TAG", matches: ["p"]}
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"]}
The Code is as follows:
Return parseOnly? SoFar. length: soFar? Sizzle. error (selector ):
TokenCache (selector, groups). slice (0 );
Because parseOnly = undefined, tokenCache (selector, groups). slice (0) is executed. This statement pushes the groups into the cache and returns its copy.
As a result, after all the parsing is completed, someone may ask, the second element is not parsed here. Yes, this needs to be parsed again in actual operation. Of course, if you want to resolve this issue. "class: contain ('span '): eq (3", save the results of the valid selector to the cache, so you can avoid re-parsing and improve the execution speed. However, this only increases the current running speed. During the execution process, when ". class: contain ('span ')" is submitted for resolution again, it will be saved to the cache.
So far, the entire execution process has been completed.