Introduction to the

Once we’ve checked the element, we need to verify that the element is correctly selected, for example, “.header >.nav. nav-item”, select the element of the “.nav-item”class attribute. We first need to determine the context, for example: You look for this element in the document root, document, in the.header context if you narrow it down, and if you find the.nav-item element, then you always check if the ancestor of the.nav-item element has a class property for nav, Continue looking for the.header class attribute from the ancestor element. If both are correct, the element was chosen correctly.

In the last section we showed you how to select an element based on a selector, but this section is really important to show you how to verify that this element is correct. Here the selected element is compiled for context validation and filtering. First of all, the CSS selector engine is shown below.

The build process

addCombinator

This function, as the name implies, is to help add combinator function, CSS combinator selector 4 kinds:

  1. (space) matches all elements after the specified element. example
  2. (>) Selects all children of the specified element. example
  3. (+) must have the same parent element, selecting the element immediately after the specified element. example
  4. The general sibling selector selects all siblings specified after the specified element. example

These four are different according to the tight relationship and add the attribute first to distinguish, you can see the demo experience above.

/ * * the matcher is a function was introduced to combinator form is an object, such as: {dir: "parentNode", first: true}, base true | false * * /
function addCombinator(matcher, combinator, base) {
    var dir = combinator.dir
    ,skip = combinator.next,
    key = skip || dir,
    checkNonElements = base && key === 'parentNode',
    doneName = done++;
    
    return combinator.first ? 
        function (elem, context, xml){... } :function (elem, context, xml){... } {var oldCache, uniqueCache, outerCache, newCache = [dirruns, doneName]
        }
}

Copy the code

It obviously returns a closure function, passing in elem,context, and XML as needed. So here’s a quick analysis of what these two different functions do.

The final identification of the function is determined by the combinator method and the parameter form of this method has three forms, the first is the initial {dir:””,next: “”} the second {dir:””,first: true} and the third {dir:””}. Dir corresponds to “”,” > “, “+”, and “~” respectively. “>” and “+” refer to child selectors and + sibling selectors respectively. The selected elements are more closely related, that is, the descendants, or the immediate siblings.

Check the nearest ancestor element or the preceding element, if first:true
Following element = true if the closest ancestor/preceding element is found and false if the preceding element is not found
function(elem, context, xml) {
        while ((elem = elem[dir])) {
                if (elem.nodeType === 1 || checkNonElements) {
                        returnmatcher(elem, context, xml); }}return false; } This method is add"", "~" selector. Find the parent element based on the element passed in// Check against all ancestor/preceding elements
function(elem, context, xml) {
    var oldCache, uniqueCache, outerCache,
            newCache = [dirruns, doneName];

 // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
 // Data cannot be set to arbitrary XML nodes
    if (xml) {
        while ((elem = elem[dir])) {
                if (elem.nodeType === 1 || checkNonElements) {
                        if (matcher(elem, context, xml)) {
                                return true; }}}}else {
        while ((elem = elem[dir])) {
            // If it is an element node, the element is marked here
            if (elem.nodeType === 1 || checkNonElements) {
                outerCache = elem[expando] || (elem[expando] = {});

                // Support: IE <9 only
                // Defend against cloned attroperties (jQuery gh-1709)
                 // Create a property for browsers smaller than IE9 to prevent cloning
                uniqueCache = outerCache[elem.uniqueID] ||
                        (outerCache[elem.uniqueID] = {});
                // If it is a fildList tag
                if (skip && skip === elem.nodeName.toLowerCase()) {
                    elem = elem[dir] || elem;
                // Check Internet Explorer
                } else if ((oldCache = uniqueCache[key]) &&
                    oldCache[0] === dirruns && oldCache[1] === doneName) {
                   
                  // Assign to newCache so results back-propagate to previous elements
                    // Assign old oldCache[2] to new oldCache[2]
                    return (newCache[2] = oldCache[2]);
                } else {

                    // Reuse newcache so results back-propagate to previous elements
                    uniqueCache[key] = newCache;

                    // A match means we're done; a fail means we have to keep checking
                    // If the match is successful, the match ends, otherwise continue next time
                    if ((newCache[2] = matcher(elem, context, xml))) {
                            return true;
                    }
                }
            }
        }
    }
    return false;
};


Copy the code

elementMatcher

Matches elements and matchers according to the element and matcher passed in

Matches is passed as the first argument to the matching context
// function (elem, context, true) {indexOf(checkContext, elem) > -1}
// function (elem, context, xml) {return checkContext === elem}
// If the element is not in the context, the match fails
function elementMatcher(matchers) {
  return matchers.length > 1? 
    function (elem, context, xml) {
    	var i = matchers.length;
      while(i--) {
        if(! matchers[i](elem, context,xml)) {return false}}return true
  	}:
  machers[0]}Copy the code

matcherFromTokens

MatcherFromTokens is a very important way to take the result of parsing to lexical parsing, and make a match for each selector, which is validation. It connects the relationship between elements and selectors.

// Get the result of parsing to lexical parsing, and make a match for each selector, i.e. validation
function matchFromTokens(tokens){
  // Check the context,
	var checkContext,matcher, j,
      // Get the number of selectors left to validate
      len = tokens.lengthm
  		// Do I need a context to view the first element, or a leading element
  		leadingRelative = Expr.relative[tokens[0].type],
      // If you need to ask 1 up or down, implicit does not need to ask 1 up or down,
      // Add an implicit leading element if not required
      impliciRelative = leadingRelative || Expr.relative[""].// The leading is 1 without 0
      // There is no leading element I is 0, there is a leading element I is 1
      i = leadingRelative ? 1 : 0.// Match the context, return true or false. Just pass in an element
      // Match the context according to whether leading elements need to be added
      matchContext = addConbinator(function (elem) {
        return elem === checkContext
      }, implicitRelative, true),// Match any number of contexts, based on the leading element passed in, if there is a leading element, get the leading element, if not add a descendant selector
      matchAnyContext = addCombinator(function (elem) {
        return indexOf(checkContext, elem) > -1
      }, implicitRelative, true),
     // The matchers array contains matchers, depending on whether there is a leading element,
     // XML or context! ==outermostContext
    // Assign the current context to checkContext to check if nodeType exists, if it does match an exact context, if it does not match any context,
      matchers = [function (elem, context, xml) {
       varret = (! leadingRelative && (xml || context ! ==outermostContext)) || (checkContext = context).nodeType ? matchContext(elem, context, xml): matchAnyContext(elem, context, xml)// empty chekContext
       	var checkContxt = null
        Return the current context
        return ret
      }]
			for (; i < len; i++) {
        // Count from the first matcher
        if ((matcher = Expr.relative[tokens[i].type])) {
          matchers = [addCombinator(elementMatcher(matchers), matcher)]
        } else {
          // Filter this selector and pass in matches ['box']
          matcher = Expr.filter([tokens[i].type]).apply(null, tokens[i].matches)
          // If the current machter has this property
          if (matcher[expando]) {
            // Proceed from the next element
            j = ++ i
            for (; j < len; j++) {
              // If it is a relative selector, it will jump out of this loop and proceed to the next processing
              if (Exp.relative[tokens[j].type]) {
                break}}return setMatcher(
            	i > 1 && elementMatcher(matchers),
              i> 1 && toSelector(
                // Tokensi-2 type is the penultimate element. Tokens are empty if they are ""
                // Remove the i-1 length and convert to selector
              	tokens.slice(0, i -1).concat({value: tokens[i -2].type === "" ? "*": ""})
              ).replace(rtrim, "$1"),
              matcher,
              i < j&& matcherFromTokens(tokens.slice(i,j )),
              j < len && matcherFronTokens((tokens = tokens.slice(j)))
              j < len && toSelector(tokens)
            )
          }
          matchers.push(matcher)
        }
      }
  return elementMatcher(matchers)
}

Copy the code

compile

The result of a lexical parsing of the selector to be checked.

compile = Sizzle.compile = function(selector, match /* Internal Use Only */ ) {
        var i,
        // Set the matcher
        setMatchers = [],
        // Element matcher
        elementMatchers = [],
        // Whether there is a compiler substitution
        cached = compilerCache[selector + ""];
        // If not compiled
        if(! cached) {// If there is no match, the result of lexical parsing is rewritten
                // Generate a function of recursive functions that can be used to check each element
                if(! match) { match = tokenize(selector); }// Check if there is a group selector
                        i = match.length;
                // Save the last group to start
                        while (i--) {
                // To match selectors from Tokens, pass in a set of selectors and return a cache of that match selector
                //compilerCache(selector) = make a cache
                //matcherFromTokens is a closure that stores the validation order for each selector and calls it when it is used.
                cached = matcherFromTokens(match[i]);
                // If the cache is already set, elementMatchers are put in
                if (cached[expando]) {
                        setMatchers.push(cached);
        // If not cached, put it into elementMatchers for final validation
                } else{ elementMatchers.push(cached); }}// Cache the result of this compilation
            // Cache the compiled function
            cached = compilerCache(
                    selector,
                    matcherFromGroupMatchers(elementMatchers, setMatchers)
            );

            // Save selector and tokenization
            cached.selector = selector;
        }
        // Returns the compiled result
        return cached;
};
Copy the code

matcherFromGroupMatchers

/** elementMatchers are matched by elements and setMatchers are compiled by data already cached **/

function matcherFromGroupMatchers (elementMatchers, setMatchers) {
    var bySet = setMatchers.length > 0 ,	
     byElement = elementMatchers.length > 0.// Super matcher
	  superMatcher = function (seed, context, xml , results, outermost) {
 			var elem , j ,matcher,
        matchedCount = 0;
        i = "0",
        unmatched = seed && [],
      	setMatched = [],
        // Outermost context
      	contextBackup = outermostContext,
        // There are cached elements or find all elements directly
        elems = seed || byElement && Expr.find["TAG"] ("*", outermost)
      	// Unique dir walks several times
      	dirrunsUnique = (dirruns += contextBackup == null ? 1: Math.random() || 0.1)
        if (outermost) {
          // support IE11+ 
          outermostContext = context == document || context || outermost
        }
      	// Pass elements to elementMathers to add directly to the result
      	for( ; i < len && (elem = elems[i]) != null; i++) {
          if (byElement && elem) {
            j = 0;
           	// Context does not exist at the same time ownerDocument! = document
            if(! context && elem.ownerDocument ! =document) {
              // Sets the context of the current elementsetDocument(elem) xml =! documentIsHTML; }while ((matcher = elementMatchers[j++])) {
              if (matcher(elem, context || document, xml)) {
                results.push(elem)
                break}}if (outermost) {
              dirruns = dirrunsUnique
            }
          }
          // Trace elements with mismatched Settings
          if (bySet) {
            // Traverses all possible matchers
            if((elem = ! matcher && elem)) { matchedCount -- }if (seed) {
              unmatched.push(elem)
            }
          }
        }
      // I is the element access counter
      matchedCount += i
      // Sets the filter for unmatched elements
      // If there are no elements that do not match (matchedCount is equal to I) unless we are not accessing any elements in the above loop because there are no element matchers and seeds
      // The low initial string "0" allows I to keep a string only in this case, which results in a "00" matchCount, different from I, but also a number 0
      if(bySet && i ! == matchedCount) { j =0;
        while ((matcher = setMatchers[j++])) {
          matcher(unmatched, setMatched, context, xml)
        }
        // Reintegrate elements to eliminate the need for sorting
        if (seed) {
          if (matchedCount > 0) {
            while(i--) {
              if(! (unmatched[i] || setMatched[i])) { setMatched[i] = pop.call(results) } } } setMatched = condense(setMatched) } /add Matches to results add matching to results push. Apply (results, setMatched)// The seed element does not exist to set the order of the successful match matcher
        if(outermost && ! seed && setMathed.length >0 &&
           (matchedCount + setMatchers.length) > 1
           ) {
          Sizzle.uniqueSort(results)
        }
        return unmatched
      }
		}
	return bySet ? markFunction(SuperMatcher): superMather;
}
Copy the code

Sizzle source code analysis (I) Basic concepts

Sizzle source code analysis (2) tool approach

Sizzle source code analysis (iii) compatibility processing

Sizzle source code analysis (4) Sizzle static method analysis

Sizzle source code analysis (5) How to select the corresponding HTML node according to the CSS selector

Sizzle source code analysis (vi) verifies that filtering results in the correct element nodes