jQuery选择器源码解读(五):tokenize的解析过程


Posted in Javascript onMarch 31, 2015

以下分析基于jQuery-1.10.2.js版本。

下面将以$("div:not(.class:contain('span')):eq(3)")为例,说明tokenize和preFilter各段代码是如何协调完成解析的。若想了解tokenize方法和preFilter类的每行代码的详细解释,请参看如下两篇文章:

下面是tokenize方法的源码,为了简便期间,我把有关缓存、逗号的匹配以及关系符的匹配的代码全部去掉了,只留了与当前例子有关的核心代码。被去掉的代码很简单,若需要可以看一下上述文章即可。

另外,代码统一写在说明文字上方。

function tokenize(selector, parseOnly) {

 var matched, match, tokens, type, soFar, groups, preFilters;

 

 soFar = selector;

 groups = [];

 preFilters = Expr.preFilter;
 while (soFar) {

  if (!matched) {

   groups.push(tokens = []);

  }

  

  matched = false;
  for (type in Expr.filter) {

   if ((match = matchExpr[type].exec(soFar))

     && (!preFilters[type] || (match = preFilters[type]

       (match)))) {

    matched = match.shift();

    tokens.push({

     value : matched,

     type : type,

     matches : match

    });

    soFar = soFar.slice(matched.length);

   }

  }
  if (!matched) {

   break;

  }

 }
 return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :

  tokenCache(selector, groups).slice(0);

}

首先,jQuery执行过程中由select方法首次调用tokenize,并将"div:not(.class:contain('span')):eq(3)"作为selector参数传入该方法。
 soFar = selector;

soFar = "div:not(.class:contain('span')):eq(3)"
第一次进入while循环时,由于matched还未被赋值,所以执行if内的如下语句体,该语句将初始化tokens变量,同时,将tokens压入groups数组。

groups.push(tokens = []); 

之后,进入for语句。

第一次for循环:从Expr.filter中取出第一个元素"TAG"赋给type变量,执行循环体代码。

   if ((match = matchExpr[type].exec(soFar))

     && (!preFilters[type] || (match = preFilters[type]

       (match)))) {

match = matchExpr[type].exec(soFar)的执行结果如下:

match =["div", "div"]

示例的第一个选择器为div,匹配matchExpr["TAG"]的正则表达式,且不存在preFilters["TAG"],故执行if内语句体。

matched = match.shift(); 

移除match中的第一个元素div,并将该元素赋予matched变量,此时matched="div",match = ["div"]

    tokens.push({

     value : matched,

     type : type,

     matches : match

    }

创建一个新对象{ value: "div", type:"TAG", matches: ["div"] },并将该对象压入tokens数组。

    soFar = soFar.slice(matched.length);

soFar变量删除div,此时,soFar=":not(.class:contain('span')):eq(3)"
第二次for循环:从Expr.filter中取出第二个元素"CLASS"赋给type变量,执行循环体代码。

   if ((match = matchExpr[type].exec(soFar))

     && (!preFilters[type] || (match = preFilters[type]

       (match)))) {

由于当前的soFar=":not(.class:contain('span')):eq(3)",不匹配CLASS类型的正则表达式,故结束本次循环。
第三次for循环:从Expr.filter中取出第三个元素"ATTR"赋给type变量,执行循环体代码。
同样,由于当前剩余选择器不是属性选择器,故结束本次循环。

第四次for循环:从Expr.filter中取出第四个元素"CHILD"赋给type变量,执行循环体代码。
同样,由于当前剩余选择器不是CHILD选择器,故结束本次循环。

第五次for循环:从Expr.filter中取出第五个元素"PSEUDO"赋给type变量,执行循环体代码。

   if ((match = matchExpr[type].exec(soFar))

     && (!preFilters[type] || (match = preFilters[type]

       (match)))) {

match = matchExpr[type].exec(soFar)的执行结果如下:
[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]

由于存在preFilters["PSEUDO"],故执行其后的代码:

match = preFilters[type](match) 

preFilters["PSEUDO"]代码如下:

"PSEUDO" : function(match) {

 var excess, unquoted = !match[5] && match[2];
 if (matchExpr["CHILD"].test(match[0])) {

  return null;

 }
 if (match[3] && match[4] !== undefined) {

  match[2] = match[4];

 } else if (unquoted

   && rpseudo.test(unquoted)

   && (excess = tokenize(unquoted, true))

   && (excess = unquoted.indexOf(")", unquoted.length

     - excess)

     - unquoted.length)) {
  match[0] = match[0].slice(0, excess);

  match[2] = unquoted.slice(0, excess);

 }
 return match.slice(0, 3);

}

传入的match参数等于:

[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", undefined, undefined, undefined, undefined, undefined
unquoted = !match[5] && match[2] 

unquoted = ".class:contain('span')):eq(3"

if (matchExpr["CHILD"].test(match[0])) {  

    return null;  

}

 match[0] = ":not(.class:contain('span')):eq(3)",不匹配matchExpr["CHILD"]正则表达式,不执行return null语句。

if (match[3] && match[4] !== undefined) {  

    match[2] = match[4];  

} 

由于match[3]和match[4]都等于undefined,故执行else的语句体。

else if (unquoted  

        && rpseudo.test(unquoted)  

        && (excess = tokenize(unquoted, true))  

        && (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length) 

 此时,unquoted = ".class:contain('span')):eq(3",为真,而且由于unquoted含有:contain('span'),与正则表达式rpseudo匹配,故rpseudo.test(unquoted)为真,然后再次调用tokenize对unquoted再次解析,如下语句:

excess = tokenize(unquoted, true) 

此次调用tokenize函数时,传入的selector参数等于".class:contain('span')):eq(3",parseOnly等于true。函数体内执行过程如下:

soFar = selector; 

 soFar = ".class:contain('span')):eq(3"
第一次进入while循环时,由于matched还未被赋值,所以执行if内的如下语句体,该语句将初始化tokens变量,同时,将tokens压入groups数组。

groups.push(tokens = []); 

之后,进入for语句。

第一次for循环:从Expr.filter中取出第一个元素"TAG"赋给type变量,执行循环体代码。

if ((match = matchExpr[type].exec(soFar))  

        && (!preFilters[type] || (match = preFilters[type]  

                (match)))) { 

由于当前剩余选择器不是TAG选择器,故结束本次循环。
第二次for循环:从Expr.filter中取出第二个元素"CLASS"赋给type变量,执行循环体代码。

match = matchExpr[type].exec(soFar)的执行结果如下:

match = ["class" , "class"]

由于不存在preFilters["CLASS"],故执行if内语句体。

matched = match.shift(); 

 移除match中的第一个元素class,并将该元素赋予matched变量,此时matched="class",match = ["class"]

tokens.push({  

    value : matched,  

    type : type,  

    matches : match  

} 

创建一个新对象{ value: "class", type:"CLASS", matches: ["class"] },并将该对象压入tokens数组。

soFar = soFar.slice(matched.length); 

soFar变量删除class,此时,soFar = ":contain('span')):eq(3"
第三次for循环:从Expr.filter中取出第三个元素"ATTR"赋给type变量,执行循环体代码。
同样,由于当前剩余选择器不是属性选择器,故结束本次循环。

第四次for循环:从Expr.filter中取出第四个元素"CHILD"赋给type变量,执行循环体代码。
同样,由于当前剩余选择器不是CHILD选择器,故结束本次循环。

第五次for循环:从Expr.filter中取出第五个元素"PSEUDO"赋给type变量,执行循环体代码。

if ((match = matchExpr[type].exec(soFar))  

        && (!preFilters[type] || (match = preFilters[type]  

                (match)))) { 

 match = matchExpr[type].exec(soFar)的执行结果如下:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]

由于存在preFilters["PSEUDO"],故执行其后的代码:

match = preFilters[type](match)

 preFilters["PSEUDO"]代码如上所示,此处不再列举。

"PSEUDO" : function(match) {  

    var excess, unquoted = !match[5] && match[2];  

  

    if (matchExpr["CHILD"].test(match[0])) {  

        return null;  

    }  

  

    if (match[3] && match[4] !== undefined) {  

        match[2] = match[4];  

    } else if (unquoted  

            && rpseudo.test(unquoted)  

            && (excess = tokenize(unquoted, true))  

            && (excess = unquoted.indexOf(")", unquoted.length  

                    - excess)  

                    - unquoted.length)) {  

  

        match[0] = match[0].slice(0, excess);  

        match[2] = unquoted.slice(0, excess);  

    }  

  

    return match.slice(0, 3);  

} 

 传入的match参数等于:
[":contain('span')", "contain", "'span'", "'", "span", undefined, undefined, undefined, undefined, undefined, undefined]

unquoted = !match[5] && match[2]; 

unquoted = "span"

 if (matchExpr["CHILD"].test(match[0])) {

  return null;

 }

由于":contain('span')"不匹配matchExpr["CHILD"]正则表达式,故不执行内部语句体。

 if (match[3] && match[4] !== undefined) {

  match[2] = match[4];

 }

 由于match[3] = "'",match[4] ="span",故执行if内部语句体,将"span"赋予match[2]

return match.slice(0, 3); 

返回match前三个元素的副本
此时回到tokenize方法的for循环内继续执行,此时各变量值如下:

match = [":contain('span')", "contain", "span"]

soFar = ":contain('span')):eq(3"

matched = match.shift(); 

 将":contain('span')"移除match数组,并赋予matched变量

tokens.push({  

    value : matched,  

    type : type,  

    matches : match  

} 

 创建一个新对象{ value:
":contain('span')", type:"PSEUDO", matches: ["contain", "span"] },并将该对象压入tokens数组。

soFar = soFar.slice(matched.length); 

soFar变量删除":contain('span')",此时,soFar="):eq(3)",之后,直至for循环结束,且再次执行while循环,也没有一个有效选择器,故退出while循环。

return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :  

    tokenCache(selector, groups).slice(0); 

 由于此时parseOnly = true,故返回此时soFar的长度6,继续执行preFilters["PSEUDO"]的代码

 

 else if (unquoted  

        && rpseudo.test(unquoted)  

        && (excess = tokenize(unquoted, true))  

        && (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)  

 

 将6赋予excess变量,然后由代码
 

 excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length  

 

 计算出:not选择器结束位置(即右括号位置)22

match[0] = match[0].slice(0, excess);  

match[2] = unquoted.slice(0, excess); 

分别计算出完整的:not选择器字符串(match[0])和其括号内的字符串(match[2]),分别等于:

match[0] = ":not(.class:contain('span'))"

match[2] = ".class:contain('span')"

return match.slice(0, 3);

返回match中前三个元素的副本。
回到tokenize函数,此时match = [":not(.class:contain('span'))", "not", ".class:contain('span')"]

matched = match.shift();

移除match中的第一个元素":not(.class:contain('span'))",并将该元素赋予matched变量,此时matched="":not(.class:contain('span'))"",
match = ["not", ".class:contain('span')"]

tokens.push({  

    value : matched,  

    type : type,  

    matches : match  

} 

 创建一个新对象{ value: ":not(.class:contain('span'))"", type:"PSEUDO", matches:  ["not", ".class:contain('span')"]  },并将该对象压入tokens数组。此时tokens共有两个元素分别是div和not选择器。

soFar = soFar.slice(matched.length); 

 soFar变量删除":not(.class:contain('span'))",此时,soFar=":eq(3)",结束本次for循环后,再次回到while循环,同样方式,获取tokens的第三个元素eq选择器,过程与not一致,这里就不再细讲了。最后的groups的结果如下:
group[0][0] = {value: "div", type: "TAG", matches: ["div"]  }

group[0][1] = {value: ":not(.class:contain('span'))", type: "PSEUDO", matches: ["not", ".class:contain('span')"] }

group[0][2] = {value: ":eq(3)", type: "PSEUDO", matches: ["eq", "3"] }

return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :  

    tokenCache(selector, groups).slice(0); 

由于parseOnly = undefined,所以执行tokenCache(selector, groups).slice(0),该语句将groups压入缓存,并返回其副本。
由此,完成了所有的解析,或许有人会问,这里第二个元素并没有解析出来呀,是的,这个需要在实际运行中再次解析。当然,这里若可以将刚才解析."class:contain('span')):eq(3"时,将有效选择器的结果保存到缓存内,那么就可以避免再次解析,提高执行速度。但这也仅仅提高了当前这次运行速度。因为在执行过程中,对".class:contain('span')"再次提交解析时,会存入缓存。

至此,整个执行过程已经全部结束。

Javascript 相关文章推荐
javascript 学习笔记(一)DOM基本操作
Apr 08 Javascript
jQuery EasyUI API 中文文档 - Menu菜单
Oct 03 Javascript
深入理解javascript中defer的作用
Dec 11 Javascript
jQuery的几个我们必须了解的特点
May 03 Javascript
js给selected添加options的方法
May 06 Javascript
JQuery中Ajax()的data参数类型实例分析
Dec 15 Javascript
Bootstrap表格和栅格分页实例详解
May 20 Javascript
AngularJs Javascript MVC 框架
Jun 20 Javascript
基于canvas的二维码邀请函生成插件
Feb 14 Javascript
微信小程序中页面FOR循环和嵌套循环
Jun 21 Javascript
AngularJS实现controller控制器间共享数据的方法示例
Oct 30 Javascript
Vue实现摇一摇功能(兼容ios13.3以上)
Jan 26 Vue.js
JavaScript制作windows经典扫雷小游戏
Mar 31 #Javascript
jQuery选择器源码解读(四):tokenize方法的Expr.preFilter
Mar 31 #Javascript
JavaScript制作简易的微信打飞机
Mar 31 #Javascript
JS获取表格内指定单元格html内容的方法
Mar 31 #Javascript
JS实现为表格动态添加标题的方法
Mar 31 #Javascript
JS实现从表格中动态删除指定行的方法
Mar 31 #Javascript
jQuery选择器源码解读(三):tokenize方法
Mar 31 #Javascript
You might like
PHP+MYSQL的文章管理系统(一)
2006/10/09 PHP
apache+php+mysql安装配置方法小结
2010/08/01 PHP
php设计模式 Singleton(单例模式)
2011/06/26 PHP
优化PHP程序的方法小结
2012/02/23 PHP
destoon调用discuz论坛中带图片帖子的实现方法
2014/08/21 PHP
浅析php静态方法与非静态方法的用法区别
2016/05/17 PHP
基于PHP制作验证码
2016/10/12 PHP
php写入文件不覆盖的实例讲解
2019/09/17 PHP
jQuery中文入门指南,翻译加实例,jQuery的起点教程
2007/01/13 Javascript
javascript开发技术大全-第3章 js数据类型
2011/07/03 Javascript
ie中js创建checkbox默认选中问题探讨
2013/10/21 Javascript
jquery复选框多选赋值给文本框的方法
2015/01/27 Javascript
jQuery UI设置固定日期选择特效代码分享
2015/08/27 Javascript
使用JQuery实现的分页插件分享
2015/11/05 Javascript
jQuery实现标题有打字效果的焦点图代码
2015/11/16 Javascript
javascript的 {} 语句块详解
2016/02/27 Javascript
深入理解JavaScript中的块级作用域、私有变量与模块模式
2016/10/31 Javascript
浅谈jQuery hover(over, out)事件函数
2016/12/03 Javascript
jQuery实现页面顶部下拉广告
2016/12/30 Javascript
从对象列表中获取一个对象的方法,依据关键字和值
2017/09/20 Javascript
Vue退出登录时清空缓存的实现
2019/11/12 Javascript
javascript实现滚轮轮播图片
2020/12/13 Javascript
[01:08:32]DOTA2-DPC中国联赛 正赛 DLG vs PHOENIX BO3 第二场 1月18日
2021/03/11 DOTA
详解Python中的装饰器、闭包和functools的教程
2015/04/02 Python
Python基于smtplib实现异步发送邮件服务
2015/05/28 Python
Python中__init__.py文件的作用详解
2016/09/18 Python
详解Python判定IP地址合法性的三种方法
2018/03/06 Python
python实现监控某个服务 服务崩溃即发送邮件报告
2018/06/21 Python
pandas的排序和排名的具体使用
2019/07/31 Python
澳大利亚购买最佳炊具品牌网站:Cookware Brands
2019/02/16 全球购物
岗位职责定义及内容
2013/11/08 职场文书
国家助学金获奖感言
2014/01/31 职场文书
感恩教育活动总结
2014/05/05 职场文书
文明美德伴我成长演讲稿
2014/05/12 职场文书
室内趣味活动方案
2014/08/24 职场文书
企业法人授权委托书
2014/09/25 职场文书