我分析的jQuery版本是1.8.3。Sizzle代码从3669行开始到5358行,将近2000行的代码,这个引擎的版本还是比较旧,最新的版本已经到v2.2.2了,代码已经超过2000行了。并且还有个专门的Sizzle主页。

从一个demo开始,HTML代码如下:

<div id="grand_father"><div id="father"><div id="child1" class="child">子集1</div><div id="child2" class="child">子集2</div><div id="child3" class="child">子集3</div><input type="radio" id="radio1"/></div>
</div>

然后JavaScript代码如下:

var $nodes = $('div + input[type="radio"],div.child:first-child');
console.log($nodes);

1)返回的是一个jQuery对象,如下图所示,并且匹配到了两个标签,一个div和radio,

2)右边的div在0的位置,radio在1的位置,这说明jQuery的选择器匹配是从右往左的!

下面看一个流程图,当我编写了$('div + input[type="radio"],div.child:first-child')后发生的过程:

一、jQuery对象

对象是需要new一下才行的,但是jQuery只要$("xxx")后,就生成了一个对象。

1)jQuery构造函数

在第42行,将会返回一个new对象:

jQuery = function( selector, context ) {// The jQuery object is actually just the init constructor 'enhanced'return new jQuery.fn.init( selector, context, rootjQuery );
}

2)jQuery对象结构

根据上面的返回对象的图中可以看到:

a. 对象的原型属性__proto__指向的是函数jQuery的原型属性prototype。__proto__ 是内部 [ [Prototype ]] ,原型链就是通过这个属性来实现的。

b. 索引是0和1的,其实是浏览器中的原生对象,我们可以搞个简单的选择器来验证,例如$("#radio1"),代码将会执行到140行

elem = document.getElementById(match[2]);
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if (elem && elem.parentNode) {// Handle the case where IE and Opera return items// by name instead of IDif (elem.id !== match[2]) {return rootjQuery.find(selector);}// Otherwise, we inject the element directly into the jQuery objectthis.length = 1;this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;

二、select函数

5116行的select函数是引擎的入口:

1)在这里引用了词法分析函数tokenize。

2)当tokenize返回的Token集合数组只有一个的时候,将会寻找种子合集【通过一些原生DOM接口可获取到】,在5147行中可以看到:

/*
完整的find在4089行,简易的find如下:
Expr.find = {'ID': context.getElementById,'CLASS': context.getElementsByClassName,'NAME': context.getElementsByName,'TAG': context.getElementsByTagName
}
*/
if ((find = Expr.find[type])) {// Search, expanding context for leading sibling combinatorsif ((seed = find(token.matches[0].replace(rbackslash, ""),rsibling.test(tokens[0].type) && context.parentNode || context,xml))) {//省略逻辑....
  }
}

3)通过compile编译函数,生成Token集合数组对应的匹配器,匹配后返回结果。

三、词法分析

高级的浏览器会直接使用querySelectorAll方法选择匹配。而低级的浏览器IE6或IE7等,就只能进入到jQuery的Sizzle引擎进行匹配。

为了调试方便,我将5182行的代码修改成“!document.querySelectorAll”,让高级浏览器也进入Sizzle引擎中匹配。

1)Token格式

4684行的tokenize函数最终返回的是Token集合数组,Token是一个String对象,格式如下:

String{0:'字符1',1:'字符2',....., type:'对应的Token类型【TAG,ID,CLASS,ATTR,CHILD,PSEUDO,NAME,>,+,空格,~】', matches:'正则匹配到的一个结构'}

type类型根据4150行的relative对象和4230行的filter对象中的key值获取。

2)返回的结果

'div + input[type="radio"],div.child:first-child'返回的数组如下:

上面返回的顺序是从左往右,先input,然后是div。

3)tokenize函数的流程

上图中有4个关系符号:

Expr.relative = {">": { dir: "parentNode", first: true }," ": { dir: "parentNode" },"+": { dir: "previousSibling", first: true },"~": { dir: "previousSibling" }
}

结合上面的HTML结构:

1)grand_father与child1属于祖宗与后代关系(空格表达)

2)father与child1属于父子关系,也算是祖先与后代关系(>表达)

3)child1与child2属于临近兄弟关系(+表达)

4)child1与child2,child3都属于普通兄弟关系(~表达)

四、编译函数

把高级规则转换成底层实现就叫编译,比如高级语言到机器语言的过程就是编译。同样把抽象的css选择语法转变成具体的匹配函数的过程也是编译。

1)matcherFromTokens

5080行的compile函数通过引用4931行的matcherFromTokens函数获取Token集合对应的匹配器,引用代码如下:

1 i = group.length;//从右往左
2 while (i--) {
3   cached = matcherFromTokens(group[i]);
4   if (cached[expando]) {
5     setMatchers.push(cached);
6   } else {
7     elementMatchers.push(cached);
8   }
9 }

返回了两个函数数组,对应上面的Token集合数组,由于是从右往左,所以与上面的Token集合数组反过来。【在4979行console.log(matchers)】

打开第一个值,会发现里面还嵌套着很多闭包,闭包里面又有闭包,这就是前面所说的大的匹配函数:

matcherFromTokens最后会引用4803行的elementMatcher,将上面的数组作为参数传递过去。

上面示例代码的第7行就在将函数插入到elementMatchers数组中。再传递给下面的matcherFromGroupMatchers函数。

2)matcherFromGroupMatchers

再引用4983行的matcherFromGroupMatchers函数生成终极匹配器,返回匹配结果。

这个函数将会return出来的一个curry化的函数,也就是4986行的superMatcher函数。

在4995行,superMatcher函数会根据参数seed 、expandContext和context确定一个起始的查询范围:

elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context )

有可能是直接从seed种子集合中获取,也有可能在context或者context的父节点范围内。

这里的context是“document”,也就是整个DOM树【在5003行console.log(elems)】,elems结构如下:

可以看出如果事先定义了content,就会把范围缩小很多,利于匹配,例如jQuery可以这样写:

$('div + input[type="radio"],div.child:first-child', $('#grand_father'))

在5007行开始过滤,elementMatchers参数就是上面返回的大匹配器。

之所以用for是因为选择器(div + input[type="radio"],div.child:first-child)中有“,”号,所以是两组大匹配器。

for (;(elem = elems[i]) != null; i++) {//省略逻辑...for (j = 0; (matcher = elementMatchers[j]); j++) {if (matcher(elem, context, xml)) {results.push(elem);break;}}//省略逻辑...
}

大致过程就是这样,里面还有很多细节地方,这里就不讨论了,有兴趣的可以自己去打打断点玩玩。

源码下载:

http://download.csdn.net/detail/loneleaf1/9379318

参考资料:

http://www.cnblogs.com/aaronjs/p/3310937.html    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理

http://foio.github.io/sizzle-research/      sizzle引擎研究

http://www.imooc.com/learn/172      jQuery源码解析

http://www.cnblogs.com/mufc-go/p/3324488.html     sizzle源码分析

http://rapheal.sinaapp.com/page/2/    jQuery源码剖析

本文转自 咖啡机(K.F.J)   博客园博客,原文链接:http://www.cnblogs.com/strick/p/5078435.html,如需转载请自行联系原作者

jQuery中的Sizzle引擎分析相关推荐

  1. jQuery中的选择器引擎Sizzle

    读Sizzle的源码,分析的Sizzle版本号是2.3.3. Sizzle的Github主页 浏览器原生支持的元素查询方法: 方法名 方法描述 兼容性描述 getElementById 根据元素ID查 ...

  2. jQuery中的选择器引擎Sizzle 1

    读Sizzle的源码,分析的Sizzle版本号是2.3.3. Sizzle的Github主页 浏览器原生支持的元素查询方法: 方法名 方法描述 兼容性描述 getElementById 根据元素ID查 ...

  3. jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + div.aaron input[type="checkb ...

  4. jQuery1.11源码分析(8)-----jQuery调用Sizzle引擎的相关API

    之所以把这部分放在这里,是因为这里用到了一些基本API,前一篇介绍过后才能使用. //jQuery通过find方法调用Sizzle引擎 //jQuery通过find方法调用Sizzle引擎 jQuer ...

  5. jQuery中hover与mouseover和mouseout的区别分析

    本文实例分析了jQuery中hover与mouseover和mouseout的区别.分享给大家供大家参考,具体如下: 以前一直以为在jquery中其实mouseover和mouseout两个事件等于h ...

  6. 虚幻引擎4中的移动设备分析插件

    虚幻引擎4中的移动设备分析插件 Joe Graf 在 2015/09/18 02:20:37 | 学习  教程  程序 Share on Facebook Share on Twitter Share ...

  7. JQuery源码分析 - 闭包机制在jQuery中的使用及冲突解决

    jQuery中的闭包机制 本系列中我们将基于jquery3.5.1版本对jQuery源码进行分析,分析以源码加注释的方式展示. 本节中将分析jQuery源码中的 14 ~ 40行:自执行函数定义.环境 ...

  8. Sizzle引擎--原理与实践(二)

    主要流程与正则 表达式分块 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['& ...

  9. 第十二课:Sizzle引擎详解

    这篇博客难度太大,跟前端开发其实没什么关系,如果你想成为大牛,那就去了解下吧.如果你还不想,那可以忽略,毕竟面试官也不会问到这里来,因为他也不太懂.呵呵. Sizzle引擎是jQuery的选择器,它大 ...

  10. 深入jQuery中的data()

    引入 data函数在jQuery中看起来很不起眼, 就像沙滩上一颗平凡的沙子, 但仔细一瞅, 却惊讶的发现data是jQuery中无比重要的一环, 甚至jQuery中各种事件都基于此. data有什么 ...

最新文章

  1. 前端、git入门至常用指令
  2. Orange‘s 一个操作系统的实现--Bochs遇到的问题
  3. Spring Cloud(6.1):搭建OAuth2 Authorization Server
  4. 3章.Linux主机规划和磁盘分区
  5. 标准单元测试步骤:A -B-C-D-E-F
  6. php 网站api,php – 如何调节网站的API用户?
  7. linux更新分区信息到内核,linux系统创建主分区、逻辑分区 、设置ext系列分区的参数以及检测分区的方法...
  8. C# Thread多线程学习
  9. gin -get请求的小示例2-Handle处理post请求
  10. 微信公众号推送的模板消息无法跳转到小程序指定页面?
  11. Rootkit Hacking Technology Defence Strategy Research
  12. 蚂蚁愚人节视频透露的真相:区块链只有科技巨头才玩得起?
  13. html实现短信验证的功能,怎样实现短信验证功能
  14. 如何解决WORD安全模式错误问题
  15. 2021年中国A2P(应用程序对个人)消息传递市场趋势报告、技术动态创新及2027年市场预测
  16. 帮表弟的女友买了个5900的dell 1420
  17. python仙修之 入门之后不放弃?
  18. 动态内存分配实现冒泡排序
  19. matlab 死区,matlab中怎么产生死区
  20. Pygame实战之外星人入侵NO.9——消灭外星人

热门文章

  1. DirectX11海洋模拟实践
  2. 硅谷中那些潜在的颠覆世界的力量
  3. 3类IP的私网地址网段
  4. 北京最新道路货物运输驾驶员考试真题题库及答案
  5. PHP加密解密方法 阿星小栈
  6. 倒计时 1 天丨微信大数据挑战赛,即将启动报名
  7. 硬核3-D视觉 - 三维视觉简介
  8. Linux下pppd拨号脚本配置
  9. 什么是Android SDK
  10. Android kotlin 时间戳转换工具