JQuery selector source code (3): tokenize method, jquerytokenize
/** The tokenize method is the core function of selector parsing, which converts the selector into two-level array groups * example: * If the selector is "div. class, span, the parsed result 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} * can be seen from the above results, the parsing result of each element of the groups selector block separated by commas. * In addition, matches in the above results equals the results of pattern matching, because it is not convenient to clearly write here, * only the code matches: match is written here. ** The tokenize method performs the following two main tasks: * 1. The resolution selector * 2. Saves the resolution result to the cache, if *** @ param selector is used for parsing the selector string * @ param parseOnly is true, this call matches the sub-selector. * For example: if the initial selector is "div: not (. class: not (: eq (4): eq (3) "* The code first matches the TAG selector div. * then, the matched pseudo selector string is: not (. class: not (: eq (4): eq (3), * the code will put ". class: not (: eq (4): eq (3 "is used as the value in the brackets of not for further parsing. * When the code calls tokenize resolution, the parseOnly parameter is set to true. */function tokenize (selector, parseOnly) {var matched, Match, tokens, type, soFar, groups, preFilters, // obtain the cached result cached = tokenCache [selector + ""]; /** if there is a selector resolution result in the cache *, execute the if statement body */if (cached) {// if the initial selector is parsed (parseOnly! = True), the cache result is returned. // if not, 0 return parseOnly? 0: cached. slice (0);}/** because strings are not processed as objects in javascript, * by assigning values, the code automatically copies a new string to soFar, * In this way, any processing of soFar will not affect the original data of selector */soFar = selector; groups = []; // assign a value here, which is only used to reduce the number of words in subsequent code, shorten the execution path preFilters = Expr. preFilter; while (soFar) {// Comma and first run/** rcomma = new RegExp ("^" + whitespace + "*," + whitespace + "*") * rcomma is used to determine whether multiple selector blocks exist, that is, multiple parallel selectors separated by commas. ** The following conditions are determined in sequence :*! Matched: true if the loop body is executed for the first time; otherwise, false. * Matched indicates whether to execute the loop body for the first time. * It also indicates whether soFar starts with an invalid string (that is, an invalid single selector) in this loop. * (Match = rcomma.exe c (soFar): gets matching items that match rcomma */if (! Matched | (match = rcomma.exe c (soFar) {if (match) {// Don't consume trailing commas as valid/** remove the first comma and all previous characters * For example: * If the initial selector is: "div. news, span. closed ", * in the parsing process, the div is first parsed by the subsequent code. news, left ", span. when closed "* is executed here in the loop body, delete the comma and the previous consecutive blank space (match [0]), and * Change soFar to" span. closed ", continue to execute the parsing process ** here, if the last non-blank character of the initial selector is a comma, * the soFar remains unchanged when the following code is executed, that is, soFar. slice (match [0]. length) returns an empty string. * The final returned result is | soFar */soFar = soFar. Slice (match [0]. length) | soFar;}/** assign tokens to an empty array when you first execute the loop body or encounter a comma delimiter, and * press it into the groups array */groups. push (tokens = []);} matched = false; // Combinators/** rcombinators = new RegExp (* "^" + whitespace + "* ([> ++] | "+ Whitespace +") "+ whitespace +" * "), * rcombinators is used to match four types of Relational operators, that is,> + ~ And blank ** if soFar starts with a relational character, execute the statement body in if */if (match = rcombinators.exe c (soFar ))) {/** remove the match array from match [0] and assign it to matched * If the original link character has spaces on both sides, in this case, match [0] and matched are not equal. * For example: * If soFar = "+. div "; * after executing match = rcombinators.exe c (soFar), * match [0] =" + ", while match [1] =" + "; * matched = match. after shift (), * matched = "+", while match [0] = "+"; */matched = match. shift (); // press the matching result into the tokens array. push ({value: matched, // Cas T descendant combinators to space/** rtrim = new RegExp ("^" + whitespace + "+ | ((?: ^ | [^ \\\\]) (? :\\\\.) *) "* + Whitespace +" + $ "," g "), * whitespace =" [\ x20 \ t \ r \ n \ f] "; ** match [0] below. replace (rtrim, "") is used to replace the blank spaces on both sides of match [0] With Spaces * but because of the match on it. shift function. match [0] is already a string with no blank strings on both sides. * The replacement is useless Code */type: match [0]. replace (rtrim, "")}); // assign the character string after the link character to soFar and continue parsing soFar = soFar. slice (matched. length);} // Filters/** use the for statement to match the IDs, tags, CLASS, CHILD, ATTR, and PSEUDO selector of soFar one by one. * If the selector matches, the pre-filter function corresponding to the selector of this type is called first., * Then, press the result into the tokens array to continue this loop. */For (type in Expr. filter) {/** match = matchexpr?type=.exe c (soFar): Call the regular expression of type to match soFar, and assign the matching result to match. If no data is matched, match is undefined. *! PreFilters [type]: If the type pre-filter function does not exist, true * match = preFilters [type] (match): pre-filter is executed, and return the result to match **/if (match = matchexpr1_type.exe c (soFar ))&&(! PreFilters [type] | (match = preFilters [type] (match) {// remove match [0] from the match array and assign it to matchedmatched = match. shift (); // press the matching result into the tokens array. push ({value: matched, type: type, matches: match}); // assign the matched strings to soFar and continue parsing soFar = soFar. slice (matched. length) ;}}/** if matched = false, * indicates that this cycle does not have a valid selector (including the type selectors such as the link operator and id and class) * therefore, soFar resolved to the current position is an invalid selector string * jumps out of the while LOOP body */if (! Matched) {break ;}// Return the length of the invalid excess // if we're re just parsing // Otherwise, throw an error or return tokens/** if the initial selector string is not parsed (! ParseOnly = true), * returns soFar. length, soFar. length indicates the final position of the continuous valid selector. * Subsequent articles will describe the instance. * If the initial selector string is parsed, check whether soFar has any characters. * If yes, execute Sizzle. error (selector) throws an exception. * If not, tokenCache (selector, groups) is executed ). slice (0) pushes the result into the cache and returns a copy of the result. */Return parseOnly? SoFar. length: soFar? Sizzle. error (selector): // Cache the token=encache (selector, groups). slice (0 );}