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 相关文章推荐
Mootools 1.2教程 Fx.Tween的使用
Sep 15 Javascript
获取焦点时,利用js定时器设定时间执行动作
Apr 02 Javascript
Javascript面向对象设计一 工厂模式
Dec 20 Javascript
js 取时间差去掉周六周日实现代码
Dec 25 Javascript
封装好的一个万能检测表单的方法
Jan 21 Javascript
jQuery插件Tooltipster实现漂亮的工具提示
Apr 12 Javascript
JS实现的车标图片提示效果代码
Oct 10 Javascript
基于javascript实现文字无缝滚动效果
Mar 22 Javascript
JavaScript运动框架 解决速度正负取整问题(一)
May 17 Javascript
Node.js 如何利用异步提升任务处理速度
Jan 07 Javascript
解决layui数据表格Date日期格式的回显Object的问题
Sep 19 Javascript
js实现石头剪刀布游戏
Oct 11 Javascript
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 数组的一个悲剧?
2011/05/11 PHP
解析如何通过PHP函数获取当前运行的环境 来进行判断执行逻辑(小技巧)
2013/06/25 PHP
PHP session_start()问题解疑(详细介绍)
2013/07/05 PHP
Javascript浅谈之引用类型
2013/12/18 Javascript
js插件方式打开pdf文件(浏览器pdf插件分享)
2013/12/20 Javascript
jQuery实现流动虚线框的方法
2015/01/29 Javascript
jQuery ui实现动感的圆角渐变网站导航菜单效果代码
2015/08/26 Javascript
谈谈对offsetleft兼容性的理解
2015/11/11 Javascript
js创建对象的方法汇总
2016/01/07 Javascript
jQuery控制frames及frame页面JS的方法
2016/03/08 Javascript
Web Uploader文件上传插件使用详解
2016/05/10 Javascript
js与jquery分别实现tab标签页功能的方法
2016/11/18 Javascript
jQuery实现弹幕效果
2017/02/17 Javascript
jQuery实现多张图片上传预览(不经过后端处理)
2017/04/29 jQuery
Bootstrap模态框插件使用详解
2017/05/11 Javascript
微信小程序使用wxParse解析html的方法示例
2019/01/17 Javascript
使用preload预加载页面资源时注意事项
2020/02/03 Javascript
vue-cli3.0实现一个多页面应用的历奇经历记录总结
2020/03/16 Javascript
vue 自定指令生成uuid滚动监听达到tab表格吸顶效果的代码
2020/09/16 Javascript
vue使用vue-quill-editor富文本编辑器且将图片上传到服务器的功能
2021/01/13 Vue.js
[51:14]LGD vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.21
2018/08/22 DOTA
有关wxpython pyqt内存占用问题分析
2014/06/09 Python
Python 功能和特点(新手必学)
2015/12/30 Python
python+selenium+autoit实现文件上传功能
2017/08/23 Python
Python实现base64编码的图片保存到本地功能示例
2018/06/22 Python
python变量的存储原理详解
2019/07/10 Python
Python将二维列表list的数据输出(TXT,Excel)
2020/04/23 Python
IntelliJ 中配置 Anaconda的过程图解
2020/06/01 Python
python实现AHP算法的方法实例(层次分析法)
2020/09/09 Python
python单例模式的应用场景实例讲解
2021/02/24 Python
面向对象编程OOP的优点
2013/01/22 面试题
店长岗位的工作内容
2013/11/12 职场文书
党支部反对四风思想汇报
2014/10/10 职场文书
无财产离婚协议书范本
2014/10/28 职场文书
2015年企业员工工作总结范文
2015/05/21 职场文书
redis缓存存储Session原理机制
2021/11/20 Redis