JQuery source code series (6) sizzle compilation

Time:2022-1-6

WelcomeMy columnView a series of articles.

compile

After talking about sizzle for so long, I always feel so bad. For a selector, we generate tokens and optimize it. The optimization steps include removing headers and generating seed sets. For these seed sets, we know that the final matching result comes from a part of the set. It seems that the next task is also clear: filter the seeds (or call it matching).

JQuery source code series (6) sizzle compilation

In fact, the matching process is very simple. It is to judge DOM elements, and the weak is the generation relationship (>) or adjacent sibling relationship (+). If it is not satisfied, it will end. If it is a descendant relationship (space) or sibling relationship (~), it will make multiple judgments, either find a correct one or end it. However, backtracking still needs to be considered.

such asdiv > div.seq h2 ~ p, they have been divided into tokens correspondingly. If each seed goes through the process, it is obviously too troublesome. A reasonable method is to generate a closure function corresponding to each judged token for unified search.

Expr.filterIt is used to generate matching functions. It is about as follows:

Expr.filter = {
  "ID": function(id){...},
  "TAG": function(nodeNameSelector){...},
  "CLASS": function(className){...},
  "ATTR": function(name, operator, check){...},
  "CHILD": function(type, what, argument, first, last){...},
  "PSEUDO": function(pseudo, argument){...}
}

Look at two examples, everything is clear:

Expr.filter["ID"] = function( id ) {
  var attrId = id.replace( runescape, funescape );
  //This returns a function
  return function( elem ) {
    return elem.getAttribute("id") === attrId;
  };
};

After analyzing tokens, if it is found that its type is ID, its ID is retained, and a check function is returned with the parameter elem, which is used to judge whether the ID of the DOM is consistent with the ID of tokens. The advantage of this approach is that it is compiled once and executed many times.

So, is that so? Let’s look at other examples:

Expr.filter["TAG"] = function( nodeNameSelector ) {
  var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
  return nodeNameSelector === "*" ?
    //Returns a function
    function() { return true; } :
    //The parameter is elem
    function( elem ) {
      return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
    };
};

Expr.filter["ATTR"] = function( name, operator, check ) {
  //Returns a function
  return function( elem ) {
    var result = Sizzle.attr( elem, name );

    if ( result == null ) {
      return operator === "!=";
    }
    if ( !operator ) {
      return true;
    }

    result += "";

    return operator === "=" ? result === check :
      operator === "!=" ? result !== check :
      operator === "^=" ? check && result.indexOf( check ) === 0 :
      operator === "*=" ? check && result.indexOf( check ) > -1 :
      operator === "$=" ? check && result.slice( -check.length ) === check :
      operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
      operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
      false;
  };
},

Last returned result:

  1. Input [type = button] attribute type is button;

  2. Input [type! = button] attribute type is not equal to button;

  3. Input [name ^ = pre] attribute name starts with pre;

  4. Input [name * = new] attribute name contains new;

  5. Input [name $= ed] attribute name ends with ED;

  6. Input [name = ~ = new] attribute name is new separated by spaces;

  7. Input [name | = zh] attribute name is either equal to zh or starts with zh followed by a hyphen -.

Therefore, for a token, a closure function is generated. The parameter received by the function is a DOM, which is used to judge whether the DOM element meets the token constraints, such as ID or classname. If multiple tokens (i.e. tokens) are processed in this way, a special function array for judgment will be obtained. In this way, for each element in the child seed, the function array can be used to judge its parent element or brother node one by one, which greatly improves the efficiency, that is, the so-called compilation once and use multiple times.

Compile source code

Paste the compile function code directly. There will bematcherFromTokensandmatcherFromGroupMatchersThese two functions are also introduced together.

var compile = function(selector, match) {
  var i,setMatchers = [],elementMatchers = [],
    cached = compilerCache[selector + " "];
  //Judge whether there is cache, as if every function will judge
  if (!cached) {
    if (!match) {
      //Judge whether match generates tokens
      match = tokenize(selector);
    }
    i = match.length;
    while (i--) {
      //Here we give tokens to this function
      cached = matcherFromTokens(match[i]);
      if (cached[expando]) {
        setMatchers.push(cached);
      } else {
        elementMatchers.push(cached);
      }
    }

    //Put in cache
    cached = compilerCache(
      selector,
      //This function generates the final matcher
      matcherFromGroupMatchers(elementMatchers, setMatchers)
    );

    // Save selector and tokenization
    cached.selector = selector;
  }
  return cached;
};

Compiling the compile function seems simple. Look at matcherfromtokens:

//
function matcherFromTokens(tokens) {
  var checkContext,matcher,j,len = tokens.length,leadingRelative = Expr.relative[tokens[0].type],
    implicitRelative = leadingRelative || Expr.relative[" "],
    i = leadingRelative ? 1 : 0,
    //Make sure that all elements can be found
    //Addcombiner is for expr Relative to judge
    /*
      Expr.relative = {
        ">": { dir: "parentNode", first: true },
        " ": { dir: "parentNode" },
        "+": { dir: "previousSibling", first: true },
        "~": { dir: "previousSibling" }
      };
     */
    matchContext = addCombinator(
      function(elem) {
        return elem === checkContext;
      },implicitRelative,true),
    matchAnyContext = addCombinator(
      function(elem) {
        return indexOf(checkContext, elem) > -1;
      },implicitRelative,true),
    matchers = [
      function(elem, context, xml) {
        var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml));
        // Avoid hanging onto element (issue #299)
        checkContext = null;
        return ret;
      }
    ];

  for (; i < len; i++) {
    //Process "empty > ~ +"
    if (matcher = Expr.relative[tokens[i].type]) {
      matchers = [addCombinator(elementMatcher(matchers), matcher)];
    } else {
      //Process attr child class ID pseudo tag. Here is the filter function
      matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);

      // Return special upon seeing a positional matcher
      //The pseudo class divides the selector into two parts
      if (matcher[expando]) {
        // Find the next relative operator (if any) for proper handling
        j = ++i;
        for (; j < len; j++) {
          if (Expr.relative[tokens[j].type]) {
            break;
          }
        }
        return setMatcher(
          i > 1 && elementMatcher(matchers),
          i > 1 && toSelector(
              // If the preceding token was a descendant combinator, insert an implicit any-element `*`
              tokens
                .slice(0, i - 1)
                .concat({value: tokens[i - 2].type === " " ? "*" : ""})
            ).replace(rtrim, "$1"),
          matcher,
          i < j && matcherFromTokens(tokens.slice(i, j)),
          j < len && matcherFromTokens(tokens = tokens.slice(j)),
          j < len && toSelector(tokens)
        );
      }
      matchers.push(matcher);
    }
  }

  return elementMatcher(matchers);
}

Addcombiner function is used to generate curry function to solve expr Relative:

function addCombinator(matcher, combinator, base) {
  var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++;

  return combinator.first ? // Check against closest ancestor/preceding element
    function(elem, context, xml) {
      while (elem = elem[dir]) {
        if (elem.nodeType === 1 || checkNonElements) {
          return matcher(elem, context, xml);
        }
      }
      return false;
    } : // 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
      if (xml) {
        while (elem = elem[dir]) {
          if (elem.nodeType === 1 || checkNonElements) {
            if (matcher(elem, context, xml)) {
              return true;
            }
          }
        }
      } else {
        while (elem = elem[dir]) {
          if (elem.nodeType === 1 || checkNonElements) {
            outerCache = elem[expando] || (elem[expando] = {});

            // Support: IE <9 only
            // Defend against cloned attroperties (jQuery gh-1709)
            uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {});

            if (skip && skip === elem.nodeName.toLowerCase()) {
              elem = elem[dir] || elem;
            } else if ((oldCache = uniqueCache[key]) && oldCache[0] === dirruns && oldCache[1] === doneName) {
              // Assign to newCache so results back-propagate to previous elements
              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 (newCache[2] = matcher(elem, context, xml)) {
                return true;
              }
            }
          }
        }
      }
      return false;
    };
}

Where the elementmatcher function is used to generate a matcher:

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;
    } : matchers[0];
}

matcherFromGroupMatchersAs follows:

function matcherFromGroupMatchers(elementMatchers, setMatchers) {
  var bySet = setMatchers.length > 0,
    byElement = elementMatchers.length > 0,
    superMatcher = function(seed, context, xml, results, outermost) {
      var elem,j,matcher,matchedCount = 0,i = "0",unmatched = seed && [],setMatched = [],
        contextBackup = outermostContext,
        // We must always have either seed elements or outermost context
        elems = seed || byElement && Expr.find["TAG"]("*", outermost),
        // Use integer dirruns iff this is the outermost matcher
        dirrunsUnique = dirruns += contextBackup == null ? 1 : Math.random() || 0.1,len = elems.length;

      if (outermost) {
        outermostContext = context === document || context || outermost;
      }

      // Add elements passing elementMatchers directly to results
      // Support: IE<9, Safari
      // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
      for (; i !== len && (elem = elems[i]) != null; i++) {
        if (byElement && elem) {
          j = 0;
          if (!context && elem.ownerDocument !== document) {
            setDocument(elem);
            xml = !documentIsHTML;
          }
          while (matcher = elementMatchers[j++]) {
            if (matcher(elem, context || document, xml)) {
              results.push(elem);
              break;
            }
          }
          if (outermost) {
            dirruns = dirrunsUnique;
          }
        }

        // Track unmatched elements for set filters
        if (bySet) {
          // They will have gone through all possible matchers
          if (elem = !matcher && elem) {
            matchedCount--;
          }

          // Lengthen the array for every element, matched or not
          if (seed) {
            unmatched.push(elem);
          }
        }
      }

      // `i` is now the count of elements visited above, and adding it to `matchedCount`
      // makes the latter nonnegative.
      matchedCount += i;

      // Apply set filters to unmatched elements
      // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
      // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
      // no element matchers and no seed.
      // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
      // case, which will result in a "00" `matchedCount` that differs from `i` but is also
      // numerically zero.
      if (bySet && i !== matchedCount) {
        j = 0;
        while (matcher = setMatchers[j++]) {
          matcher(unmatched, setMatched, context, xml);
        }

        if (seed) {
          // Reintegrate element matches to eliminate the need for sorting
          if (matchedCount > 0) {
            while (i--) {
              if (!(unmatched[i] || setMatched[i])) {
                setMatched[i] = pop.call(results);
              }
            }
          }

          // Discard index placeholder values to get only actual matches
          setMatched = condense(setMatched);
        }

        // Add matches to results
        push.apply(results, setMatched);

        // Seedless set matches succeeding multiple successful matchers stipulate sorting
        if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) {
          Sizzle.uniqueSort(results);
        }
      }

      // Override manipulation of globals by nested matchers
      if (outermost) {
        dirruns = dirrunsUnique;
        outermostContext = contextBackup;
      }

      return unmatched;
    };

  return bySet ? markFunction(superMatcher) : superMatcher;
}

This process is too complicated. Please forgive me for not being able to read it patiently…

Leave your name first and analyze later…

At this point, in fact, it can be over, but with a responsible attitude, let’s take care of the whole process of sizzle.

Although sizzle is an independent project, it is represented in jQueryjQuery.findFunction, these two functions are actually the same and completely equivalent. Then it introduces the tokensize function, which is called lexical analysis. Its function is to divide the selector into an array of tokens. Each element of the array has value and type values. Then there is the select function, which plays an optimization role, removing the head and tail, and expr The find function generates the seed array.

The following introduction is careless. I read a lot and understand it very well. The compile function performs precompiling, which is to generate a closure function for the remaining selectors after removing seed, and generate a large supermatcher function from the closure function. This function can be used at this timesuperMatcher(seed)To process seed and get the final result.

So what is supermatcher?

superMatcher

As already said, this iscompile()()Function, andcompile()The return value of is supermatcher. Whether introducing matcherfromtokens or matcherfromgroupmatchers, the result is to generate super matches and then process seed. This is a test moment. Only if it can withstand the screening will it stay.

summary

The following is a flowchart summarized by others:

JQuery source code series (6) sizzle compilation

First step

div > p + div.aaron input[type="checkbox"]

Go through expr first from the far right Find obtains the seed array, where the input is tag, sogetElementsByTagName()Function.

Step 2

Reorganize the selector. At this time, remove the selector after input:

div > p + div.aaron [type="checkbox"]

Step 3

At this time, through expr Relative divides tokens into close relationships and non close relationships according to the relationship. For example, [“>”, “+”] is a close relationship, and its first = true. For [“”, “~”] is a non close relationship. Tight relationships can be quickly judged during screening.

Matcherfromtokens compiles closure functions according to the relationship, which are divided into four groups:

div > 
p + 
div.aaron 
input[type="checkbox"]

The compilation function mainly uses expr Filter and expr relative。

Step 4

Put all the compiled closure functions together to generate the supermatcher function.

function( elem, context, xml ) {
    var i = matchers.length;
    while ( i-- ) {
        if ( !matchers[i]( elem, context, xml ) ) {
            return false;
        }
    }
    return true;
}

From right to left, process the seed collection. If there is a mismatch, it returns false. If the match is successful, it indicates that the seed element meets the filter conditions and is returned to results.

reference resources

JQuery 2.0.3 source code analysis sizzle engine – compilation function (large space)
JQuery 2.0.3 source code analysis sizzle engine – Super matching

This paper is based on GitHubSource addressWelcome to star.

WelcomeMy blogcommunication.

Recommended Today

Redis Client On Error: Error: write ECONNABORTED Config right

Solve the redis client on error: error: write econnaborted config rightwe Problem Description: Solution: 1. First, check whether the firewall of Linux is turned on Turn off the firewall [[email protected]]# systemctl stop firewalld.service Open 6379 port number [[email protected]]# sudo firewall-cmd –zone=public –add-port=6379/tcp –permanent success [[email protected]]# sudo firewall-cmd –reload success 2. Check whether the redis startup […]