博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
jQuery中的Sizzle引擎分析
阅读量:5835 次
发布时间:2019-06-18

本文共 4632 字,大约阅读时间需要 15 分钟。

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

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

子集1
子集2
子集3

然后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 #6963if (elem && elem.parentNode) {  // Handle the case where IE and Opera return items  // by name instead of ID  if (elem.id !== match[2]) {    return rootjQuery.find(selector);  }  // Otherwise, we inject the element directly into the jQuery object  this.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 combinators  if ((seed = find(      token.matches[0].replace(rbackslash, ""),      rsibling.test(tokens[0].type) && context.parentNode || context,      xml    ))) {    //省略逻辑....  }}

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

 

三、词法分析

高级的浏览器会直接使用方法选择匹配。而低级的浏览器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;    }  }  //省略逻辑...}

 

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

源码下载:

 

参考资料:

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

      sizzle引擎研究

      jQuery源码解析

     sizzle源码分析

    jQuery源码剖析

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

你可能感兴趣的文章
此博客不再发表对自己私事的看法
查看>>
导致Asp.Net站点重启的10个原因
查看>>
【PMP】Head First PMP 学习笔记 第一章 引言
查看>>
抓住云机遇编排工作 搞定复杂IT工作流
查看>>
MYSQL的longtext字段能放多少数据?
查看>>
MTK 平台上如何给 camera 添加一种 preview size
查看>>
云计算最大难处
查看>>
关于数据分析思路的4点心得
查看>>
Memcached安装与配置
查看>>
美团数据仓库的演进
查看>>
SAP被评为“大数据”预测分析领军企业
查看>>
联想企业网盘张跃华:让文件创造业务价值
查看>>
记录一次蚂蚁金服前端电话面试
查看>>
直播源码开发视频直播平台,不得不了解的流程
查看>>
Ubuntu上的pycrypto给出了编译器错误
查看>>
聊聊flink的RestClientConfiguration
查看>>
在CentOS上搭建git仓库服务器以及mac端进行克隆和提交到远程git仓库
查看>>
測試文章
查看>>
Flex很难?一文就足够了
查看>>
【BATJ面试必会】JAVA面试到底需要掌握什么?【上】
查看>>