jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——词法解析
jQuery源码9600多行,而Sizzle引擎就独占近2000行,占了1/5。Sizzle引擎、jQuery事件机制、ajax是整个jQuery的核心,也是jQuery技术精华的体现。里面的有些策略确实很值得学习,先膜拜之,然后细细学习。
在学习Sizzle引擎之前我们先准备一点知识,和先了解Sizzle引擎的一点工作原理。
<div id="chua">
<a>
<span>chua的测试用例</span>
</a>
<p class="group">
<label for="age">年龄:</label>
<input type="text" id="age" value="20" readonly/>
<p>
</div>
Sizzle引擎查找节点时CSS 选择器时一定要从右往左解析?
我们要获取for="age"的label标签,css可以这么写#chua > a + .group labe[for="age"]。
正常流程我们是先查找#chua,然后找到直接子节点中为a标签的节点,然后在找到紧跟着的class为.group的节点,最后找到子节点为label且for属性为age的节点。可以想象当节点很多的时候,选择器引擎干了半天终于找到满足前面条件的.group节点,最后发现下面的子节点没有节点为label且for属性为age的节点,一夜回到解放前,前面的工作白做的。
所以我们的想法就是:先看找到匹配最右边的css的节点(这一步已经缩小了很大范围),如果木有那么很好,到此结束,直接返回;如果存在,我再往左的css一个个匹配,反正在此过程中如果有不满足条件的我就踢掉。首先我们要肯定的是查找直接子节点我们要遍历所有子节点来匹配,而查找直接父节点则简单的多只有一个(能匹配的上就OK,不能匹配就丢掉),扩展到查找后代节点和祖先节点也是一样。所以从右往左查找快速得多。
现在进入正题
Sizzle引擎的在处理css选择器的时候有个原则:如果能使用浏览器原生的解析器来解析CSS选择器就使用之,不能的才使用Sizzle自定的解析方式来解析。可以肯定的是Sizzle引擎号称业界最快的CSS选择器解析引擎,但也快不过浏览器自带的解析器。
a.Sizzle入口,内部函数Sizzle( selector, context, results, seed )
Sizzle函数是所有解析的入口。根据代码可知。Sizzle对一些简单情况作了处理,不需要进行select解析就直接返回结果。不能使用浏览器提供的基础函数解析的才进入select函数。
大概的轮毂如下
function Sizzle( selector, context, results, seed ) {
... if ( !selector || typeof selector !== "string" ) {return results; }
if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {return [];} if ( !documentIsXML && !seed ) {
//rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;对于简单搜索快捷解析
if ( (match = rquickExpr.exec( selector )) ) {
// Speed-up: Sizzle("#ID"),如果是ID查询的话查询直接返回结果
if ( (m = match[1]) ) {
...
return results;
// Speed-up: Sizzle("TAG"),如果是直接查询TAG,查询直接返回结果
} else if ( match[2] ) {
...
return results;
}
// QSA path,如果能使用浏览器高级查询querySelectorAll
if ( support.qsa && !rbuggyQSA.test(selector) ) {
...
if ( newSelector ) {
//使用querySelectorAll能成功查询则返回结果,否则使用select
try {
push.apply( results, slice.call( newContext.querySelectorAll(newSelector), 0 ) );
return results;
} catch(qsaError) {
} finally {
if ( !old ) {
context.removeAttribute("id");
}
}
}
}
}
// All others
return select( selector.replace( rtrim, "$1" ), context, results, seed );
} }
上面的处理可以看到有三个情况是可以直接使用浏览器自带的处理的:查询ID(getElementById)、查询TAG(getElementsByTagName)、浏览器支持高级查询querySelectorAll。其他情况进入Sizzle自定义解析方式。
我们不禁要问:为啥木有包括Class查询?说明一下,老版本的浏览器中不是所有的浏览器都支持Class查询,但是ID和TAG查询是所有浏览器都支持的。而支持使用Class查询(getElementsByClassName)的浏览器基本都支持querySelectorAll高级查询,so,不用说,用querySelectorAll即可。
b. 词法解析
Sizzle引擎解析CSS选择器的第一个步骤就是要将CSS选择器分解成一个一个单独的词,这和编译原理共通。
Sizzle的词法解析入口函数是内部函数tokenize。tokenize的作用是把CSS选择器(其实也就是一段字符串)解析成一组基础词法的序列。这个序列里面的每一个元素格式是
Token:{
value:'匹配到的字符串',
type:'对应的Token类型',
matches:'正则匹配到的一个结构'
}
Sizzle查询结果缓存机制
jQuery在词法解析函数tokenize开始解析之前用到了一个比较巧妙的地方,Sizzle把每次查询结果缓存了起来,如果下一次有相同的查询,则直接使用缓存中的查询结果,而不需要重新查询。
机制的实现:
var tokenCache = createCache(),
...
function createCache() {
var cache, keys = [];
return (cache = function( key, value ) {
// 使用 (key + " ")避免命名冲突,最大缓存Expr.cacheLength:50 if ( keys.push( key += " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key ] = value);
});
}
... //设置缓存
tokenCache( selector, groups ); //tokenize函数中获取缓存
cached = tokenCache[ selector + " " ]
解析:
createCache()返回的是一个回调函数,对于这个回调函数来说cache,和keys都是他的类全局变量,在回调函数中可以直接使用。这里的巧妙在于return (cache = function( key, value )…),将cache赋值给了tokenCache,这样使本来不能在外面使用的cache变成了tokenCache,tokenCache保存的就是最新的缓存,直接调用tokenCache[key]即可访问缓存。
这里还有一个点就是return (cache = function( key, value )…)中cache先被赋值,然后被填充cache[key].如果是先填充cache[key],然后再赋值则cache会被新赋值覆盖。
tokenize函数详解
tokenize函数功能是做词法分析,步骤如下
首先,查看缓存中是否有上次同样的查询的结果,如果有则返回即可
cached = tokenCache[ selector + " " ];
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
然后,判断是否还有没有等待词法分析的字符串,如果没有则返回词法分析结果并将结果缓存下来;如果还有则去掉字符串前面可能存在的的逗号和空白符(比如CSS选择器为“p , span”,词法分析完p后剩下等待分析的字符串为" , span")
//如果soFar有内容则继续进行切分,知道将选择器切存储在groups中
while ( soFar ) {
//第一次执行或者开始位置有逗号
//rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" )
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// 去除前面的逗号
soFar = soFar.slice( match[0].length ) || soFar;
}
//在groups中新加一个数组类型的元素来保存新的tokens
groups.push( tokens = [] );
}
...
}
}
...
// 如果仅是解析,返回剩余的字符串长度
// 否则抛出错误或返回词法解析结果 tokens
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error( selector ) :
//缓存词法解析结果tokens
tokenCache( selector, groups ).slice( 0 );
接着,在确认还有字符串需要解析的基础上,如果第一个字符是匹配父子兄弟选择器(">"/"+"/" "/"~"),将设置每一个词语的token属性压入tokens数组并将其从带解析的字符串sofar中剔除掉。
// rcombinators=/^[\x20\t\r\n\f]*([\x20\t\r\n\f>+~])[\x20\t\r\n\f]*/
//如果匹配父子兄弟选择器
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push( {
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
} );
soFar = soFar.slice( matched.length );
}
}
然后,解析其他词语。其他词语指的是下面几类ATTR/CHILD/ClASS/ID/PSEUDO/TAG,分别对应属性/子选择器/类/ID/伪类/标签
jquery有一个比较优秀的地方就是使用了很多js正则表达式,节省了很多代码,结构上也比较清晰。如下图展示的几个CSS选择器词语对应的正则
使用matchExpr[type]将每一种用到的词语匹配的结果match列出来做一下对比
["ATTR"].exec("[type='text']"): ["[type='text']", "type", "=", "'", "text", undefined]
["CHILD"].exec(":nth-of-type(2)"): [":nth-of-type(2)", "nth", "of-type", "2", "", undefined, undefined, "", "2"]
["CLASS"].exec(".chua"): [".chua", "chua"] ["ID"].exec("#chua"): ["#chua", "chua"] ["PSEUDO"].exec(":first"): [":first", "first", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined] ["TAG"].exec("p"): ["p", "p"]
可以看出其中ATTR/CHILD/PSEUDO和另外的三种格式很不一样,为了后期便于统一处理,jQuery使用Expr.preFilter(具体的Expr.preFilter请查看源码)对这三种词语匹配的结果进行了调整,调整结果如下
match = preFilters["ATTR"]( match ): ["[type='text']", "type", "=", "text"]
match = preFilters["CHILD"]( match ): [":nth-of-type(2)", "nth", "of-type", "2", 0, 2, undefined, "", "2"] match = preFilters["PSEUDO"]( match ): [":first", "first", undefined]
匹配后将解析结果压入tokens中,并剔除已解析的词语
matched = match.shift();
tokens.push( {
value: matched,
type: type,
matches: match
} );
soFar = soFar.slice( matched.length );
好了,现在我们来看一下完成词法解析后我们得到的结果吧
以#chua > a + .group labe[for="age"]为例:
如果是多组CSS选择器如:"p > .chua , #chen input"这样使用逗号分隔的,则会得到一个二维数组[tokens1,tokens2],tokens1表示两个"p > .chua"的词法解析结果,tokens2表示 "#chen input"的词法解析结果
词法解析到此完毕。附上完整源码
function tokenize( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
//查缓存
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
} soFar = selector;
groups = [];
preFilters = Expr.preFilter; //如果soFar有内容则继续进行切分,知道将选择器切存储在groups中
while ( soFar ) {
//第一次执行或者开始位置有逗号
//rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" )
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// 去除前面的逗号
soFar = soFar.slice( match[0].length ) || soFar;
}
//在groups中新加一个数组类型的元素来保存新的tokens
groups.push( tokens = [] );
}
matched = false; // rcombinators=/^[\x20\t\r\n\f]*([\x20\t\r\n\f>+~])[\x20\t\r\n\f]*/
//如果匹配父子兄弟选择器
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push( {
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
} );
soFar = soFar.slice( matched.length );
} //filters
////其中Expr.filter中的过滤函数目前只在matcherFromTokens中用到
for ( type in Expr.filter ) {
//matchExpr包括ID, CLASS, NAME, TAG, ATTR,CHILD,PSEUDO,needsContext
//等类型的正则表达式,matchExpr中属性的排列顺序是根据使用频率来排行的
//节约查找时间
//如果通过正则匹配到了Token格式:match = matchExpr[ type ].exec( soFar )
//然后看看需不需要预处理:!preFilters[ type ]
//如果需要 ,那么通过预处理器将匹配到的处理一下 : match = preFilters[ type ]( match )
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;
}
}
// 如果仅是解析,返回剩余的字符串长度
// 否则抛出错误或返回词法解析结果 tokens
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error( selector ) :
//缓存词法解析结果tokens
tokenCache( selector, groups ).slice( 0 );
如果觉得本文不错,请点击右下方【推荐】!
jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——词法解析的更多相关文章
- mybatis源码分析(三)------------映射文件的解析
本篇文章主要讲解映射文件的解析过程 Mapper映射文件有哪几种配置方式呢?看下面的代码: <!-- 映射文件 --> <mappers> <!-- 通过resource ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- jQuery源码分析系列(转载来源Aaron.)
声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...
- jQuery源码分析系列——来自Aaron
jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...
- jquery2源码分析系列
学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
随机推荐
- Linux 安装PHP PECL 百分百成功
1. 下载 需要安装的组件 http://pecl.php.net/packages.php 2. 解压 tar zxf 你的扩展包路径 3. 进入你解压的扩展包路径后 访问 /usr/bin ...
- Third Day(上班第四天):Android开发环境配置问题相关
换公司新电脑了,重新安装Android开发环境,并配置,具体流程如下:1.百度JDK,访问Oracle官网:http://www.oracle.com/technetwork/java/javase/ ...
- LVS DR模式 RealServer 为 Windows 2008 R2配置
有3篇文档详细介绍 http://kb.linuxvirtualserver.org/wiki/Windows_Servers_in_LVS/DR_and_LVS/TUN_Clusters http: ...
- ABP理论学习之通知系统
返回总目录 本篇目录 介绍 订阅通知 发布通知 用户通知管理者 实时通知 通知存储 通知定义 介绍 通知(Notification)用于告知用户系统中的特定事件.ABP提供了基于实时通知基础设施的发布 ...
- 轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)
在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Head ...
- C#之委托与事件
委托与事件 废话一堆:网上关于委托.事件的文章有很多,一千个哈姆雷特就有一千个莎士比亚,以下内容均是本人个人见解. 1. 委托 1.1 委托的使用 这一小章来学习一下怎么简单的使用委托,了解一些基本的 ...
- [ASP.NET MVC 小牛之路]14 - Unobtrusive Ajax
Ajax (Asynchronous JavaScript and XML 的缩写),如我们所见,这个概念的重点已经不再是XML部分,而是 Asynchronous 部分,它是在后台从服务器请求数据的 ...
- Dynamics CRM导出数据到Excel
原创地址:http://www.cnblogs.com/jfzhu/p/4276212.html 转载请注明出处 Pivot Table是微软BI的一个重要工具,所以这里讲一下Dynamics CRM ...
- 【VC++技术杂谈008】使用zlib解压zip压缩文件
最近因为项目的需要,要对zip压缩文件进行批量解压.在网上查阅了相关的资料后,最终使用zlib开源库实现了该功能.本文将对zlib开源库进行简单介绍,并给出一个使用zlib开源库对zip压缩文件进行解 ...
- SqlServer2008到期升级企业版 密钥+图解
最近使用SQL Server2008,结果Sql Server Management Studio提示过期了,如图: 遇到如上图情况,需要将SQL Server2008升级维护下,还是输入原来的密钥就 ...