jQuery selector source code interpretation (iii) : tokenize method
- 2020-05-24 05:09:09
- OfStack
/*
* tokenize The method is the core function of selector resolution, which converts the selector into two series groups
* For example:
* If the selector is" div.class,span ", then the result after parsing is:
* group[0][0] = {type:'TAG',value:'div',matches:match}
* group[0][1] = {type:'CLASS',value:'.class',matches:match}
* group[1][0] = {type:'TAG',value:'span',matches:match}
* As can be seen from the above results, groups Each of the 1 The parsing result of a comma-separated selector block,
* In addition, in the above results matches Is equal to the result of pattern matching. Since it is not convenient to write clearly here,
* So just put the code matches:match Let me write it over here.
*
* tokenize Methods accomplish the following two main tasks:
* 1 Parse the selector
* 2 , the parsing results are stored in the cache for later use
*
*
* @param selector The selector string to parse
* @param parseOnly for true , indicating that the call is a matching child selector
* For example, if the initial selector is "div:not(.class:not(:eq(4))):eq(3)"
* The code matches first TAG The selector div .
* And then you match it pseudo The selector string is :not(.class:not(:eq(4))):eq(3) .
* The code will put" .class:not(:eq(4))):eq(3 "As a not The value in parentheses goes in 1 Step for parsing,
* At this point the code is calling tokenize Analytical, parseOnly The parameter will be passed in true.
*/
function tokenize(selector, parseOnly) {
var matched, match, tokens, type, soFar, groups, preFilters,
// Gets the results in the cache
cached = tokenCache[selector + " "];
/*
* If it's in the cache selector The corresponding parsing result
* execute if The statements in the body
*/
if (cached) {
// If the initial selector is resolved ( parseOnly!=true ), then returns the cache result,
// If not, return 0
return parseOnly ? 0 : cached.slice(0);
}
/*
* Because the string is javascript Is not handled as an object,
* So by assignment, the code is automatically copied 1 I gave you a new string soFar .
* In this way, the soFar None of the treatment will affect selector Original data
*/
soFar = selector;
groups = [];
// This assignment is only used to reduce the number of words in subsequent code and shorten the execution path
preFilters = Expr.preFilter;
while (soFar) {
// Comma and first run
/*
* rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*")
* rcomma Used to determine the existence of multiple selector blocks, i.e. multiple juxtaposed selectors separated by commas
*
* The following conditional decisions are in order:
* !matched : if the first 1 The secondary execution loop body is true ; Otherwise, for false .
* Here, matched As whether or not to 1 The id of the secondary execution loop body,
* Also as part of this loop soFar Whether to illegal string (that is, not legal single 1 The flag at the beginning of the selector.
* (match = rcomma.exec(soFar) : get the match rcomma The match is
*/
if (!matched || (match = rcomma.exec(soFar))) {
if (match) {
// Don't consume trailing commas as valid
/*
* Remove the first 1 A comma and all the characters before it
* Here's an example:
* If the initial selector is: "div.news,span.closed" .
* In the parsing process, the subsequent code is first parsed div.news And the rest ",span.closed"
* At this point in the loop body, place the comma and the successive Spaces before and after ( match[0] ) delete,
* make soFar become "span.closed" , and continue with the parsing process
*
* Here, if at the end of the initial selector 1 The non-white space characters are commas,
* So when you execute the following code soFar The same, that is, soFar.slice(match[0].length) Returns an empty string,
* So what you end up with is || At the back of the soFar
*/
soFar = soFar.slice(match[0].length) || soFar;
}
/*
* In the first 1 The next time the loop body is executed or a comma separator is encountered, the tokens The assignment for 1 An empty array,
* At the same time into groups An array of
*/
groups.push(tokens = []);
}
matched = false;
// Combinators
/*
* rcombinators = new RegExp(
* "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),
* rcombinators Used to match 4 The species relation character, namely >+~ And blank
*
* if soFar Is executed if it begins with a relationship character if Inside the body of a statement
*/
if ((match = rcombinators.exec(soFar))) {
/*
* will match[0] remove match Array and assign it at the same time matched
* If there are Spaces on either side of the original character, then match[0] with matched It's not equal
* Here's an example:
* if soFar = " + .div";
* perform match = rcombinators.exec(soFar) Later,
* match[0] = " + " And the match[1]="+";
* after matched = match.shift() Later,
* matched=" + " And the match[0]="+";
*/
matched = match.shift();
// Press the match in tokens In the array
tokens.push({
value : matched,
// Cast descendant combinators to space
/*
* rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)"
* + whitespace + "+$", "g"),
* whitespace = "[\\x20\\t\\r\\n\\f]";
*
* The following match[0].replace(rtrim, " ") The effect is to match[0] Replace the white space on the left and right sides with a space
* But because of the match.shift The role of, match[0] It's already a string with no white space on either side,
* Therefore, the replacement is code that has no purpose
*/
type : match[0].replace(rtrim, " ")
});
// Assigns the string following the relational character soFar , continue to parse
soFar = soFar.slice(matched.length);
}
// Filters
/*
* The following through for The statement of soFar One by one 1 matching ID , TAG , CLASS , CHILD , ATTR , PSEUDO Type selector
* If there is a match, the pre-filter function corresponding to the type selector is called first,
* Then, press the result in tokens Array, continue the loop.
*/
for (type in Expr.filter) {
/*
* match = matchExpr[type].exec(soFar) : soFar call type A pair of regular expressions of type soFar To match,
* And assign the matching result match . If no data is matched, then match for undefined .
* !preFilters[type] : if it doesn't exist type The prefilter function of type, is true
* match = preFilters[type](match) : performs prefiltering and returns the results to match
*
*/
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(match)))) {
// will match[0] remove match Array and assign it at the same time matched
matched = match.shift();
// Press the match in tokens In the array
tokens.push({
value : matched,
type : type,
matches : match
});
// Assigns the string after the match soFar , continue to parse
soFar = soFar.slice(matched.length);
}
}
/*
* if matched==false .
* There are no valid selectors for this loop (including relational and id , class Isotype selector)
* Therefore, resolve to the current location left over soFar Is an invalid selector string
* Jump out of the while The loop body
*/
if (!matched) {
break;
}
}
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
/*
* If the original selector string is not parsed ( !parseOnly==true ),
* It returns soFar.length For the time of, soFar.length Represents the final position of the continuously valid selector,
* Subsequent articles will illustrate with examples
* If you parse the initial selector string soFar Are there any more characters,
* If so, execute Sizzle.error(selector) Throw an exception;
* If not, execute tokenCache(selector, groups).slice(0) Press the result into the cache and return a copy of the result.
*/
return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :
// Cache the tokens
tokenCache(selector, groups).slice(0);
}