JQuery Sizzle引擎源代码分析
最近在拜读艾伦在慕课网上写的JQuery课程,感觉在国内对JQuery代码分析透彻的人没几个能比得过艾伦。有没有吹牛?是不是我说大话了?
什么是Sizzle引擎?
我们经常使用JQuery的选择器查询元素,查询的选择器有简单也有复杂:
简单点:“div”、“.navi”、“div.navi”。
复杂点:"div input[type='checkbox']"、"div.navi + .section p"。
Query实现查询时也是优先使用DOM标准查询函数,例如:
document.getElementById()
document.getElementsByTagName()
document.getElementsByClassName()
document.getElementsByName()
高级浏览器还实现了:
querySelector()
querySelectorAll()
由于浏览器版本差异导致的兼容问题,上面的函数并不是所有浏览器都支持。但JQuery得解决这些问题,所以就引入了Sizzle引擎。
JQuery在筛选元素时优先使用浏览器自带的高级查询函数,因为查询效率高。其次才选择使用Sizzle引擎筛选元素。
Sizzle引擎的目的是根据传入的selector选择器筛选出元素集合。执行过程经过词法分析、编译过程。通过词法分析把一个selector字符串分解成结构化的数据以便编译过程使用。编译过程充分利用了Javascript的闭包功能,生成一个函数链,在最终匹配时再去执行这个函数链。
举个例子,一个选择器selector的值为”Aaron input[name=ttt]”,通过词法分析,得到一个结构化数组:
[
{
matches: ["div"],
type: "TAG",
value: "Aaron"
},
{
type: " ",
value: " "
},
{
matches: ["name", "=", "ttt"],
type: "ATTR",
value: "[name=ttt]"
}
]
selector中的input作为一个种子集合seed。意思是Sizzle根据input查询出所有input元素,结果存放到seed集合,编译过程都是在seed集合中查询过滤。
上面说的很粗糙,不便于理解,接下来我们就拿代码来介绍。
通过代码分析原理
申明:下面的代码来源于Aaron在慕课网上的Jquery教程
compile
/**
* 编译过程
*/
function compile(){
var seed = document.querySelectorAll("input"),
selector = "Aaron [name=ttt]",
elementMatchers = [],
match = [
{
matches: ["div"],
type: "TAG",
value: "Aaron"
},
{
type: " ",
value: " "
},
{
matches: ["name", "=", "ttt"],
type: "ATTR",
value: "[name=ttt]"
}
];
elementMatchers.push(matcherFromTokens(match));
//超级匹配器
var cached = matcherFromGroupMatchers(elementMatchers);
var results = cached(seed);
results[0].checked = 'checked';
}
JQuery的compile函数包含了所有的执行过程,由于本篇介绍的重点是编译过程,所以词法分析的过程未包含,这里直接写了match结果,实际JQuery会调用tokenize()函数获取词组。
函数中调用了两个函数:matcherFromTokens()和matcherFromGroupMatchers()。
matcherFromTokens():返回的是一个函数,函数结构如下:
返回函数格式为function(elem, context, xml),并且这个函数返回一个bool值表示elem是否匹配有效。
matcherFromGroupMatchers():函数代码很简单,遍历seed集合,每个元素都调用elementMatcher函数。最终返回一个匹配成功的元素集合。
由于matcherFromGroupMatchers()函数比较简单,所以就先介绍它。
matcherFromGroupMatchers
function matcherFromGroupMatchers(elmentMatchers){
return function(seed){
var results = [];
var matcher, elem;
for(var i = 0; i < seed.length; i++){
var elem = seed[i];
matcher = elmentMatchers[0];
if(matcher(elem)){
results.push(elem);
}
} return results;
}
}
遍历seed元素,每一个元素都调用matcher函数,返回true则添加到results数组中。
matcherFromTokens
function matcherFromTokens(tokens){
var len = tokens.length,
matcher,
matchers = [];
for(var i = 0; i < len; i++){
if(tokens[i].type === " "){
matchers = [addCombinator(elementMatcher(matchers))];
}else{
matcher = filter[tokens[i].type].apply(null, tokens[i].matches);
matchers.push(matcher);
}
} return elementMatcher(matchers);
}
整个编译的核心也就在matcherFromTokens函数中,遍历分词tokens数组,分词分两大类,关系型和非关系型。关系型包括:“ ”、“>”、“+”、“~”。 剩下的都是非关系型分词。
每一个非关系型分词都会对应一个matcher:
第一个分词类型为TAG,在filter中找到matcher。第二个分词为关系分词,调用addCombinator合并之前的matcher。第三个分词类型为ATTR,在filter中找到matcher。最终matchers的值为:
在return的时候又调用了elementMatcher()函数,返回的结果还是一个函数。上面介绍compile函数时看到过返回的函数结构。
matcherFromTokens函数体中有用到addCombinator()和elementMatcher()函数以及filter对象。先看filter:
var filter = {
ATTR: function(name, operator,check){
return function(elem){
var attr = elem.getAttribute(name);
if(operator === "="){
if(attr === check){
return true;
}
}
return false;
}
},
TAG: function(nodeNameSelector){
return function(elem){
return elem.nodeName && elem.nodeName.toLowerCase() === nodeNameSelector;
}
}
}
一目了然,一看就知道filter中的ATTR和TAG对应了match分词组中的type类型,所以filter对应了非关系型分词的matcher函数。
addCombinator
function addCombinator(matcher){
return function(elem, context, xml){
while(elem = elem["parentNode"]){
if(elem.nodeType === 1)
//找到第一个亲密的节点,立马就用终极匹配器判断这个节点是否符合前面的规则
return matcher(elem);
}
}
}
addCombinator对应关系型分词matcher。本例只列举了祖先和子孙关系" "的合并,返回的结果是一个签名为function(elem, contenxt, xml)的函数,函数中elem[“parentNode”]找到文档元素类型的父节点,再调用matcher校验这个父节点是否匹配。
所以关系型matcher函数执行的过程:先通过关系类型找到匹配元素,然后再调用matcher校验匹配结果。
elementMatcher
function elementMatcher(matchers){
return matchers.length > 1 ?
function(elem, context, xml){
var i = matchers.length;
while(i--){
if(!matchers[i](elem, context, xml)){
return false;
}
}
return true;
}:
matchers[0];
}
elementMatcher()函数就是干实事的家伙,它遍历matchers函数数组,执行每个matcher函数,一旦有matchers[i]返回false则整个匹配就失败了。这里需要注意的一点是i--,为什么是反序遍历?因为JQuery Sizzle匹配的原则是从右往左。由于前面的match数组是按照选择器从左往右保存的,所以这里先执行最后面的。
上面所有的代码只是简单模拟了JQuery Sizzle引擎的执行过程,真实的源代码很复杂,估计只有大神些才能领悟透彻。大神,艾伦得算一个。
说到闭包,如果能把JQuery Sizzle代码分析透彻,理解闭包易如反掌。本篇介绍的函数返回值都是函数,而每个返回函数需要的变量都是通过闭包存储起来,在真正执行函数的时候再读取这些变量。
如果本篇内容对大家有帮助,请点击页面右下角的关注。如果觉得不好,也欢迎拍砖。你们的评价就是博主的动力!下篇内容,敬请期待!
JQuery Sizzle引擎源代码分析的更多相关文章
- Sizzle.filter [ 源代码分析 ]
最近的研究已Sizzle选择,对于原理中我们也不得不佩服! Sizzle中间filter办法.主要负责元素表达式过滤块的集合,在内部的方法调用Sizzle.selector.fitler滤波操作的操作 ...
- jquery ui widget 源代码分析
jquery ui 的全部组件都是基于一个简单,可重用的widget. 这个widget是jquery ui的核心部分,有用它能实现一致的API.创建有状态的插件,而无需关心插件的内部转换. $.wi ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 高效查询
为什么Sizzle很高效? 首先,从处理流程上理解,它总是先使用最高效的原生方法来做处理 HTML文档一共有这么四个API: getElementById 上下文只能是HTML文档 浏览器支持情况:I ...
- jQuery中的Sizzle引擎分析
我分析的jQuery版本是1.8.3.Sizzle代码从3669行开始到5358行,将近2000行的代码,这个引擎的版本还是比较旧,最新的版本已经到v2.2.2了,代码已经超过2000行了.并且还有个 ...
- jQuery 2.0.3 源码分析Sizzle引擎解析原理
jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...
- [转]JQuery - Sizzle选择器引擎原理分析
原文: https://segmentfault.com/a/1190000003933990 ---------------------------------------------------- ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则. 简 ...
- jQuery1.11源码分析(8)-----jQuery调用Sizzle引擎的相关API
之所以把这部分放在这里,是因为这里用到了一些基本API,前一篇介绍过后才能使用. //jQuery通过find方法调用Sizzle引擎 //jQuery通过find方法调用Sizzle引擎 jQuer ...
- jQuery选择器引擎和Sizzle介绍
一.前言 Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中.我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍. ...
随机推荐
- C# Excel导入、导出【源码下载】
本篇主要介绍C#的Excel导入.导出. 目录 1. 介绍:描述第三方类库NPOI以及Excel结构 2. Excel导入:介绍C#如何调用NPOI进行Excel导入,包含:流程图.NOPI以及C#代 ...
- nginx+php的使用
原文来自:windows下配置nginx+php环境 按照他的步骤走,亲测可用! 但是这里他后面说的根目录可能有些人有点懵. 其实在设置的时候就设置了: 网站根目录就是www这个目录,如果没创建请自行 ...
- 从零开始编写自己的C#框架(28)——建模、架构与框架
文章写到这里,我一直在犹豫是继续写针对中小型框架的设计还是写些框架设计上的进阶方面的内容?对于中小型系统来说,只要将前面的内容进行一下细化,写上二三十章具体开发上的细节,来说明这个通用框架怎么开发的就 ...
- 多线程条件通行工具——AbstractQueuedSynchronizer
本文原创,转载请注明出处! 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> AbstractOw ...
- 页面布局class常见命名规范
头:header 内容:content/container 尾:footer 导航:nav 侧栏:sidebar 栏目:column 页面外围控制整体布局宽度:wrapper 左右中:left rig ...
- 使用四元数解决万向节锁(Gimbal Lock)问题
问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...
- PC虚拟现实应用的性能分析与优化:从CPU角度切入
如今,虚拟现实 (VR) 技术正日益受到欢迎,这主要得益于遵循摩尔定律的技术进步让这一全新体验在技术上成为可能.尽管虚拟现实能给用户带来身临其境般的超凡体验,但相比传统应用,其具有双目渲染.低延迟.高 ...
- 我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗
文章背景,回答提问:我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗? 我的建议是这样:1. 不要辞职.首先说,你对整个开发没有一个简单的了解,或一个系统的入门学习.换句 ...
- Mono 4.0 Mac上运行asp.net mvc 5.2.3
Mono 4.0 已经发布,二进制包已经准备好,具体的发布说明参见:http://www.mono-project.com/docs/about-mono/releases/4.0.0/. 今天在Ma ...
- 在多线程编程中lock(string){...}隐藏的机关
常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 lock(订单号) 来保证对该订单的操作同时只允许一个线程执行. 这样的想法很好,至少比 lock( ...