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 相关文章推荐
用javascript实现画板的代码
Sep 05 Javascript
JavaScript URL参数读取改进版
Jan 16 Javascript
jQuery中实现动画效果的基本操作介绍
Apr 16 Javascript
nullJavascript中创建对象的五种方法实例
May 07 Javascript
ajax与302响应代码测试
Oct 23 Javascript
纯JS实现旋转图片3D展示效果
Apr 12 Javascript
利用jQuery实现漂亮的圆形进度条倒计时插件
Sep 30 Javascript
JQuery对ASP.NET MVC数据进行更新删除
Jul 13 Javascript
js记录点击某个按钮的次数-刷新次数为初始状态的实例
Feb 15 Javascript
微信小程序swiper实现滑动放大缩小效果
Nov 15 Javascript
JS原型和原型链原理与用法实例详解
Feb 05 Javascript
JS删除对象中某一属性案例详解
Sep 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
thinkphp3查询mssql数据库乱码解决方法分享
2014/02/11 PHP
php实现字符串反转输出的方法
2015/03/14 PHP
php 修改上传文件大小限制实例详解
2016/10/23 PHP
各种常用的JS函数整理
2013/10/25 Javascript
$.each遍历对象、数组的属性值并进行处理
2014/07/18 Javascript
js分页工具实例
2015/01/28 Javascript
javascript格式化json显示实例分析
2015/04/21 Javascript
Javascript对象Clone实例分析
2015/06/09 Javascript
深入探究JavaScript中for循环的效率问题及相关优化
2016/03/13 Javascript
js实现四舍五入完全保留两位小数的方法
2016/08/02 Javascript
详解XMLHttpRequest(一)同步请求和异步请求
2016/09/14 Javascript
JS实现非首屏图片延迟加载的示例
2018/01/06 Javascript
小程序自定义日历效果
2018/12/29 Javascript
vue keep-alive实现多组件嵌套中个别组件存活不销毁的操作
2020/10/30 Javascript
python基础教程之获取本机ip数据包示例
2014/02/10 Python
Python使用百度API上传文件到百度网盘代码分享
2014/11/08 Python
Python itertools模块详解
2015/05/09 Python
Python操作MySQL数据库9个实用实例
2015/12/11 Python
linecache模块加载和缓存文件内容详解
2018/01/11 Python
在Mac下使用python实现简单的目录树展示方法
2018/11/01 Python
python合并已经存在的sheet数据到新sheet的方法
2018/12/11 Python
Python学习笔记之For循环用法详解
2019/08/14 Python
HTML5 canvas基本绘图之绘制阴影效果
2016/06/27 HTML / CSS
html5 兼容IE6结构的实现代码
2012/05/14 HTML / CSS
英国领先的男装设计师服装独立零售商:Repertoire Fashion
2020/10/19 全球购物
工商管理专业职业生涯规划
2014/01/01 职场文书
市场营销毕业生自荐信范文
2014/04/01 职场文书
高中美术教师事迹材料
2014/08/22 职场文书
计算机实训报告范文
2014/11/05 职场文书
2015年圣诞节活动总结
2015/03/24 职场文书
2016年领导干部正风肃纪心得体会
2015/10/09 职场文书
详解Python牛顿插值法
2021/05/11 Python
利用Pycharm连接服务器的全过程记录
2021/07/01 Python
python基础之错误和异常处理
2021/10/24 Python
对象析构函数__del__在Python中何时使用
2022/03/22 Python
Golang实现可重入锁的示例代码
2022/05/25 Golang