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);
}


Related articles: