jQuery selector source code interpretation (8) : addCombinator function

  • 2020-05-19 04:17:14
  • OfStack

function addCombinator(matcher, combinator, base)

1, the source


function addCombinator(matcher, combinator, base) {
 var dir = combinator.dir, checkNonElements = base
   && dir === "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);
   }
  }
 } :  // Check against all ancestor/preceding elements
 function(elem, context, xml) {
  var data, cache, outerCache, dirkey = dirruns + " " + doneName;   // We can't set arbitrary data on XML nodes, so they don't
  // benefit from dir 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] = {});
     if ((cache = outerCache[dir])
       && cache[0] === dirkey) {
      if ((data = cache[1]) === true
        || data === cachedruns) {
       return data === true;
      }
     } else {
      cache = outerCache[dir] = [ dirkey ];
      cache[1] = matcher(elem, context, xml)
        || cachedruns;
      if (cache[1] === true) {
       return true;
      }
     }
    }
   }
  }
 };
}

2, functionality,

Generates the execution function of the relationship selector.

3, parameters,

matcher - a continuous array of filter selector matching functions that are used to match whether the nodes obtained through the position relation meet the selector requirements. In actual execution, this function might be the elementMatcher(matchers) generated before the relationship selector. For example: div map > span, encountered in Sizzle compilation > When, the compiled function of div.map is used as the first parameter to call the addCombinator function to check whether the acquired parent node of span meets the two conditions of div.map.

combinator -- the relationship selector corresponds to the value in Expr.relative, and the values of the various relationship selectors in Expr.relative are as follows. Use the first property of this parameter to determine whether you are returning a function that checks only the immediate object or a function that traverses all possible objects. The node of the specified location relationship is obtained by the following code: elem = elem[dir], where dir is equal to combinator.dir.


Expr.relative : {
 ">" : {
  dir : "parentNode",
  first : true
 },
 " " : {
  dir : "parentNode"
 },
 "+" : {
  dir : "previousSibling",
  first : true
 },
 "~" : {
  dir : "previousSibling"
 }
}

base -- this parameter starts with combinator.dir1 and determines the value of the variable checkNonElement. The code is as follows. This value literally means that the element currently being examined is not DOM, which is when elem.nodeType! When =1, if the value is true, the matching function will be executed; otherwise, the loop will end.

4. Return function

4.1 if the relationship selector is > Or +, returns the following function:


function(elem, context, xml) {
 while ((elem = elem[dir])) {
  if (elem.nodeType === 1 || checkNonElements) {
   return matcher(elem, context, xml);
  }
 }
}

4.4.1 function
If the element type node (checkNonElements==false) is checked, the first element type node (elem.nodeType == 1) of the specified location relation of elem is obtained iteratively. The matching function is performed to check whether the node meets the requirements.

If all types of nodes (i.e. checkNonElements==true) are checked, the adjacent node of the specified location relationship of elem is obtained, and the matching function is performed to check whether this node meets the requirements. If it meets the requirements, true is returned; otherwise, false is returned.
Some people might ask, isn't it the immediate relationship? So why do you have iterative fetch in your code? This is because individual browsers will treat the newline between the node text as TextNode, so in the process, you need to skip these nodes until the next element node.
4.1.2 parameter
elem -- single node element to examine.

context -- the context node that performs the entire selector string matching, most of the time for no purpose.

xml -- is the current search object HTML or XML documents, if HTML, xml parameter is false.

4.2 if the relationship selector is ~ or space, the following function is returned:


//Check against all ancestor/preceding elements
function(elem, context, xml) {
 var data, cache, outerCache, dirkey = dirruns + " " + doneName;  // We can't set arbitrary data on XML nodes, so they don't
 // benefit from dir 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] = {});
    if ((cache = outerCache[dir])
      && cache[0] === dirkey) {
     if ((data = cache[1]) === true
       || data === cachedruns) {
      return data === true;
     }
    } else {
     cache = outerCache[dir] = [ dirkey ];
     cache[1] = matcher(elem, context, xml)
       || cachedruns;
     if (cache[1] === true) {
      return true;
     }
    }
   }
  }
 }
};

2 function

If you are examining an XML document, the procedure follows from 4.1 return function 1, as shown in if (XML) {... } the code inside the brackets.

If the document is HTML, match the current element according to matcher. If the match is successful, return true. Otherwise return false.

4.2.2 parameters
elem -- single node element to examine.

context -- the context node that performs the entire selector string matching, most of the time for no purpose.

xml -- the current search object is HTML or XML documents; if HTML, xml parameter is false.

4.2.3 code description

Internal variables

dirkey -- key used to cache node detection results. During one execution, if a node has been checked, the test result (true or false) will be recorded in the dirkey attribute of the node (the value of the attribute is dirkey). Therefore, when the node is encountered again during this execution, it is not necessary to test again. The reason for the need for caching is that multiple nodes will have the same parent node or sibling node. The use of caching can reduce The Times of detection and improve performance.

dirruns -- each execution of precompiled code organized by matcherFromGroupMatchers generates a pseudorandom number to distinguish between different executions.
doneName - each time the addCombinator function is executed, the done variable is added 1 to distinguish the different positional relation matching functions generated.

cachedruns -- used to record the number of DOM elements in the match. For example: div map > span, which has three elements that conform to the span selector, is executed for each element > When matching functions, cachedruns is 0, 1, and 2 in turn. According to the code, the function of cachedruns can be directly understood as that in the process of matching the same element with elementMatchers in one execution process, when the same element is encountered again, the result of mismatch can be directly obtained. However, I can't think of any situation where this will happen. If anyone comes across, please let me know, thank you!

Code interpretation


while ((elem = elem[dir])) {
 if (elem.nodeType === 1 || checkNonElements) {
  // if elem The node's expando If the attribute does not exist, an empty object is assigned and assigned at the same time outerCache
  // if elem The node's expando Attribute exists, and its value is assigned outerCache
  outerCache = elem[expando] || (elem[expando] = {});
  /*
   * if outCache[dir] It has a value, and it has a value 1 The elements are equal to the current dirkey .
   *     It indicates that the current location selector has detected this node in the execution process, so execute if Gets the result directly from the cache
   * if outCache[dir] Does not exist, or no 1 Elements are not equal to the current dirkey .
   *     It indicates that the current position selector has not detected this node in the execution process. Execute else Matches the node and puts the result into the cache
   */
  if ((cache = outerCache[dir])
    && cache[0] === dirkey) {
   // If the detection result in the cache is equal to true or cachedruns Returns the detection result (no true All is false ),
   // Otherwise continue on the loop fetch 1 The nodes that conform to the position relation are matched
   if ((data = cache[1]) === true
     || data === cachedruns) {
    return data === true;
   }
  } else {
   // The array [ dirkey ] give outerCache[dir] and cache
   cache = outerCache[dir] = [ dirkey ];
   // Will match successfully, will true give cache[1] , otherwise will cachedruns The value of the gift cache[1]
   cache[1] = matcher(elem, context, xml)
     || cachedruns;
   // If the match result is true , the return true , otherwise continue on the loop fetch 1 The nodes that conform to the position relation are matched
   if (cache[1] === true) {
    return true;
   }
  }
 }
}


Related articles: