jQuery选择器源码解读(八):addCombinator函数

function addCombinator(matcher, combinator, base)

1、源码


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、功能

生成关系选择器的执行函数。

3、参数

matcher——位置关系前连续的过滤选择器匹配函数数组,该函数用于匹配通过位置关系获得的节点是否符合选择器要求。在实际执行过程中,该函数可能是关系选择器前已生成的elementMatcher(matchers)。例如:div.map>span,在Sizzle编译遇到>时,会将div.map的编译函数作为第一个参数调用addCombinator函数,用以检查获取的span父节点是否满足div.map这两个条件。

combinator——关系选择器对应Expr.relative中的值,Expr.relative中各种关系选择器的值如下。使用该参数的first属性来确定返回的是仅检查紧邻对象的函数还是遍历所有可能对象的函数。将通过如下代码:elem = elem[dir],获取指定位置关系的节点,其中dir等于combinator.dir。


Expr.relative : {

 ">" : {

  dir : "parentNode",

  first : true

 },

 " " : {

  dir : "parentNode"

 },

 "+" : {

  dir : "previousSibling",

  first : true

 },

 "~" : {

  dir : "previousSibling"

 }

}

base——该参数与combinator.dir一起,确定变量checkNonElement的值,代码如下。该值从字面理解为当前检查的是非DOM元素,就是当elem.nodeType!=1的时候,若该值为true,则会执行匹配函数,否则结束本次循环。

4、返回函数

4.1 若关系选择器是>或+,则返回如下函数:


function(elem, context, xml) {

 while ((elem = elem[dir])) {

  if (elem.nodeType === 1 || checkNonElements) {

   return matcher(elem, context, xml);

  }

 }

}

4.1.1 功能
若检查element类型节点(即checkNonElements==false),迭代获取elem指定位置关系的第一个element类型节点(elem.nodeType == 1),执行匹配函数,检查该节点是否符合要求,若符合返回true,否则返回false;

若检查所有类型节点(即checkNonElements==true),获取elem指定位置关系的紧邻节点,执行匹配函数,检查该节点是否符合要求,若符合返回true,否则返回false;
有些人或许会问,不是说是紧邻关系吗?那代码中为何要出现迭代获取这一过程呢?这是因为,个别浏览器会把节点文本之间的换行符看成是TextNode,故在处理过程中,需要跳过这些节点,直到下一个element节点。
4.1.2 参数
elem——待检查的单个节点元素。

context——执行整个选择器字符串匹配的上下文节点,大部分时候是没有用途。

xml——当前搜索对象是HTML还是XML文档,若是HTML,则xml参数为false。

4.2  若关系选择器是~或空格,则返回如下函数:


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

4.2.1 功能

若检查的是XML文档,则其过程与4.1返回函数一致,见上述代码中if ( XML ) { ... }中大括号内的代码。

若是HTML文档,则根据matcher匹配当前元素,若匹配成功,返回true;否则返回false。

4.2.2 参数
elem——待检查的单个节点元素。

context——执行整个选择器字符串匹配的上下文节点,大部分时候是没有用途。

xml——当前搜索对象是HTML还是XML文档,若是HTML,则xml参数为false。

4.2.3 代码说明

内部变量

dirkey——缓存节点检测结果用的键。在一次执行过程中,若一个节点被检查过,则会在这个节点的dirkey属性(属性名称为dirkey的值)中记录下检测结果(true或false),那么在本次执行过程中,再次遇到该节点时,不需要再次检测了。之所以需要缓存,因为多个节点会存在同一个父节点或兄弟节点,利用缓存可以减少检测的次数,提高性能。

dirruns——每次执行通过matcherFromGroupMatchers组织的预编译代码时都会产生一个伪随机数,用以区别不同的执行过程。
doneName——每次执行addCombinator函数时,done变量都会加1,用以区别生成的不同的位置关系匹配函数。

cachedruns——用来记录本次匹配是第几个DOM元素。例如:div.map>span,有3个元素符合span选择器,则针对每个元素执行>匹配函数时,cachedruns依次为0、1、2。cachedruns的作用按照代码可以直接理解为在一个执行过程中,针对同一个元素使用elementMatchers进行匹配过程中,再次遇到同一个元素时,可以直接从获取不匹配的结果,但是,我想不出哪个情况下会发生这种事情。若有人遇到,请告知,多谢!

代码解释


while ((elem = elem[dir])) {

 if (elem.nodeType === 1 || checkNonElements) {

  // 若elem节点的expando属性不存在,则赋予空对象,并同时赋予outerCache

  // 若elem节点的expando属性存在,则将其值赋予outerCache

  outerCache = elem[expando] || (elem[expando] = {});

  /* 

   * 若outCache[dir]有值,且其第一个元素等于当前的dirkey,

   *     则说明当前位置选择器在本次执行过程中已检测过该节点,执行if内的语句,从缓存中直接获取结果

   * 若outCache[dir]不存在,或第一个元素不等于当前的dirkey,

   *     则说明当前位置选择器在本次执行过程中还未检测过该节点,执行else内的语句,匹配节点并将结果放入缓存

   */

  if ((cache = outerCache[dir])

    && cache[0] === dirkey) {

   // 若缓存中检测结果等于true或cachedruns的值,则返回检测结果(非true皆为false),

   // 否则继续循环获取上一个符合位置关系的节点进行匹配

   if ((data = cache[1]) === true

     || data === cachedruns) {

    return data === true;

   }

  } else {

   // 将数组[ dirkey ]赋予outerCache[dir]及cache

   cache = outerCache[dir] = [ dirkey ];

   // 将匹配成功,将true赋予cache[1],否则将cachedruns的值赋予cache[1]

   cache[1] = matcher(elem, context, xml)

     || cachedruns;

   // 若匹配结果为true,则返回true,否则继续循环获取上一个符合位置关系的节点进行匹配

   if (cache[1] === true) {

    return true;

   }

  }

 }

}