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过滤特殊字符输入适合输入、粘贴、拖拽多种情况
Mar 22 Javascript
javascript调试之DOM断点调试法使用技巧分享
Apr 15 Javascript
浅谈javascript中createElement事件
Dec 05 Javascript
bootstrapValidator.min.js表单验证插件
Feb 09 Javascript
Angular 4依赖注入学习教程之FactoryProvider配置依赖对象(五)
Jun 04 Javascript
详谈ES6中的迭代器(Iterator)和生成器(Generator)
Jul 31 Javascript
微信小程序 input表单与redio及下拉列表的使用实例
Sep 20 Javascript
vue利用better-scroll实现轮播图与页面滚动详解
Oct 20 Javascript
微信小程序实现跑马灯效果完整代码(附效果图)
May 30 Javascript
解决webpack dev-server不能匹配post请求的问题
Aug 24 Javascript
vue实例中data使用return包裹的方法
Aug 27 Javascript
vue项目打包之开发环境和部署环境的实现
Apr 23 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
Destoon模板制作简明教程
2014/06/20 PHP
详解Laravel5.6 Passport实现Api接口认证
2018/07/27 PHP
PHP单元测试框架PHPUnit用法详解
2019/01/23 PHP
10款新鲜出炉的 jQuery 插件(Ajax 插件,有幻灯片、图片画廊、菜单等)
2011/06/08 Javascript
用jquery方法操作radio使其默认选项是否
2013/09/10 Javascript
JavaScript的null和undefined区别示例介绍
2014/09/15 Javascript
JavaScript中实现PHP的打乱数组函数shuffle实例
2014/10/11 Javascript
JavaScript函数详解
2014/11/17 Javascript
js获取元素外链样式的方法
2015/01/27 Javascript
js实现基于正则表达式的轻量提示插件
2015/08/29 Javascript
JS实现的简洁二级导航菜单雏形效果
2015/10/13 Javascript
JQuery判断正整数整理小结
2017/08/21 jQuery
vue几个常用跨域处理方式介绍
2018/02/07 Javascript
vue父组件点击触发子组件事件的实例讲解
2018/02/08 Javascript
nodejs实现聊天机器人功能
2019/09/19 NodeJs
解决vue cli4升级sass-loader(v8)后报错问题
2020/07/30 Javascript
Python 异常处理的实例详解
2017/09/11 Python
利用python画出折线图
2018/07/26 Python
Pyqt5 基本界面组件之inputDialog的使用
2019/06/25 Python
python实现两张图片拼接为一张图片并保存
2019/07/16 Python
Docker部署Python爬虫项目的方法步骤
2020/01/19 Python
Python中sys模块功能与用法实例详解
2020/02/26 Python
python读取多层嵌套文件夹中的文件实例
2020/02/27 Python
python 中不同包 类 方法 之间的调用详解
2020/03/09 Python
Python数据相关系数矩阵和热力图轻松实现教程
2020/06/16 Python
Bibloo奥地利:购买女装、男装、童装、鞋和配件
2018/10/18 全球购物
捷克街头、运动和滑板一站式商店:BoardStar.cz
2019/10/06 全球购物
Cocopanda波兰:购买化妆品、护肤品、护发和香水
2020/05/25 全球购物
研究生自荐信
2013/10/09 职场文书
厨房工作人员岗位职责
2013/11/15 职场文书
《晏子使楚》教学反思
2014/02/08 职场文书
应聘编辑自荐信范文
2014/03/12 职场文书
读书演讲主持词
2014/03/18 职场文书
我的未来不是梦演讲稿
2014/09/02 职场文书
农业项目投资意向书
2015/05/09 职场文书
python实现简易名片管理系统
2021/04/11 Python