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


Posted in Javascript onMarch 31, 2015

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;

   }

  }

 }

}
Javascript 相关文章推荐
最近项目写了一些js,水平有待提高
Jan 31 Javascript
JavaScript 图片预览效果 推荐
Dec 22 Javascript
JavaScript执行效率与性能提升方案
Dec 21 Javascript
原生JS实现响应式瀑布流布局
Apr 02 Javascript
使用jquery实现仿百度自动补全特效
Jul 23 Javascript
Javascript模仿淘宝信用评价实例(附源码)
Nov 26 Javascript
Yarn的安装与使用详细介绍
Oct 25 Javascript
浅谈 Vue v-model指令的实现原理
Jun 08 Javascript
浅谈JavaScript作用域和闭包
Sep 18 Javascript
vue-cli 自定义指令directive 添加验证滑块示例
Oct 19 Javascript
基于angular-utils-ui-breadcrumbs使用心得(分享)
Nov 03 Javascript
webpack@v4升级踩坑(小结)
Oct 08 Javascript
JS显示表格内指定行html代码的方法
Mar 31 #Javascript
jQuery选择器源码解读(七):elementMatcher函数
Mar 31 #Javascript
jQuery选择器源码解读(六):Sizzle选择器匹配逻辑分析
Mar 31 #Javascript
jQuery选择器源码解读(五):tokenize的解析过程
Mar 31 #Javascript
JavaScript制作windows经典扫雷小游戏
Mar 31 #Javascript
jQuery选择器源码解读(四):tokenize方法的Expr.preFilter
Mar 31 #Javascript
JavaScript制作简易的微信打飞机
Mar 31 #Javascript
You might like
PHP生成指定长度随机数最简洁的方法
2014/07/14 PHP
PHP Mysqli 常用代码集合
2016/11/12 PHP
Laravel中七个非常有用但很少人知道的Carbon方法
2017/09/21 PHP
PHP的mysqli_ssl_set()函数讲解
2019/01/23 PHP
详解php伪造Referer请求反盗链资源
2019/01/24 PHP
jQuery 1.9移除了$.browser可以使用$.support来替代
2014/09/03 Javascript
FF(火狐)浏览器无法执行window.close()解决方案
2014/11/13 Javascript
JSON取值前判断
2014/12/23 Javascript
JavaScript设计模式之建造者模式介绍
2014/12/28 Javascript
js实现鼠标划过给div加透明度的方法
2015/05/25 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
js 获取json数组里面数组的长度实例
2017/10/31 Javascript
再谈Angular4 脏值检测(性能优化)
2018/04/23 Javascript
用Cordova打包Vue项目的方法步骤
2019/02/02 Javascript
关于JS解构的5种有趣用法
2019/09/05 Javascript
js中forEach,for in,for of循环的用法示例小结
2020/03/14 Javascript
JS+CSS实现动态时钟
2021/02/19 Javascript
[02:54]DOTA2亚洲邀请赛 VG战队出场宣传片
2015/02/07 DOTA
python实现将英文单词表示的数字转换成阿拉伯数字的方法
2015/07/02 Python
Python实现快速多线程ping的方法
2015/07/15 Python
python生成器用法实例详解
2019/11/22 Python
keras获得model中某一层的某一个Tensor的输出维度教程
2020/01/24 Python
Python错误的处理方法
2020/06/23 Python
Python tempfile模块生成临时文件和临时目录
2020/09/30 Python
python Yaml、Json、Dict之间的转化
2020/10/19 Python
python 基于wx实现音乐播放
2020/11/24 Python
详解background属性的8个属性值(面试题)
2020/11/02 HTML / CSS
利用HTML5中的Canvas绘制一张笑脸的教程
2015/05/07 HTML / CSS
南非最受欢迎的时尚品牌:MRP
2016/09/18 全球购物
新西兰杂志订阅:isubscribe
2019/08/26 全球购物
英国手工制作的现代与经典的沙发和床:Love Your Home
2020/09/26 全球购物
Java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类
2012/02/06 面试题
茶叶店创业计划书范文
2014/01/19 职场文书
公务员群众路线专题民主生活会发言材料
2014/09/17 职场文书
和谐家庭事迹材料
2014/12/20 职场文书
分享CSS盒子模型隐藏的几种方式
2022/02/28 HTML / CSS