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 单选框,多选框美化代码
Aug 01 Javascript
js识别不同浏览器基于userAgent做判断
Jul 29 Javascript
在JavaScript应用中使用RequireJS来实现延迟加载
Jul 01 Javascript
javascript检查某个元素在数组中的索引值
Mar 30 Javascript
详解Javascript ES6中的箭头函数(Arrow Functions)
Aug 24 Javascript
用vue构建多页面应用的示例代码
Sep 20 Javascript
vue 组件 全局注册和局部注册的实现
Feb 28 Javascript
vue项目动态设置页面title及是否缓存页面的问题
Nov 08 Javascript
Three.js实现3D机房效果
Dec 30 Javascript
Angular8基础应用之表单及其验证
Aug 11 Javascript
vue项目中极验验证的使用代码示例
Dec 03 Javascript
原生js实现购物车
Sep 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
php采集速度探究总结(原创)
2008/04/18 PHP
Wordpress php 分页代码
2009/10/21 PHP
PHP中extract()函数的妙用分析
2012/07/11 PHP
浅谈php安全性需要注意的几点事项
2014/07/17 PHP
从零开始学YII2框架(三)扩展插件yii2-gird
2014/08/20 PHP
Laravel实现autoload方法详解
2017/05/07 PHP
js实现动态改变字体大小代码
2014/01/02 Javascript
javascript中的循环语句for语句深入理解
2014/04/04 Javascript
jQuery实现鼠标单击网页文字后在文本框显示的方法
2015/05/06 Javascript
AngularJS的ng Http Request与response格式转换方法
2016/11/07 Javascript
jQuery插件echarts实现的多柱子柱状图效果示例【附demo源码下载】
2017/03/04 Javascript
bootstrap表单示例代码分享
2017/05/18 Javascript
angular4 JavaScript内存溢出问题
2018/03/06 Javascript
vue-cli或vue项目利用HBuilder打包成移动端app操作
2020/07/29 Javascript
使用grappelli为django admin后台添加模板
2014/11/18 Python
Python使用pymysql小技巧
2017/06/04 Python
异步任务队列Celery在Django中的使用方法
2018/06/07 Python
Python爬虫包BeautifulSoup简介与安装(一)
2018/06/17 Python
python3实现域名查询和whois查询功能
2018/06/21 Python
Python使用Selenium模块实现模拟浏览器抓取淘宝商品美食信息功能示例
2018/07/18 Python
python 将list转成字符串,中间用符号分隔的方法
2018/10/23 Python
Python编程中flask的简介与简单使用
2018/12/28 Python
感知器基础原理及python实现过程详解
2019/09/30 Python
python实现实时视频流播放代码实例
2020/01/11 Python
matlab 计算灰度图像的一阶矩,二阶矩,三阶矩实例
2020/04/22 Python
Python3自动生成MySQL数据字典的markdown文本的实现
2020/05/07 Python
美国休闲服装品牌:Express
2016/09/24 全球购物
潘多拉珠宝美国官方网站:Pandora US
2020/06/18 全球购物
索引覆盖(Index Covering)查询含义
2012/02/18 面试题
测试驱动开发的主要步骤是什么
2014/12/10 面试题
高考自主招生自荐信
2013/10/20 职场文书
教师个人剖析材料
2014/02/05 职场文书
安全技术说明书
2014/05/09 职场文书
工作检讨书范文
2015/01/23 职场文书
大学生受助感言
2015/08/01 职场文书
检讨书之工作不认真
2019/08/14 职场文书