一.前言正则表达式
DOM选择器(Sizzle)是jQuery框架中很是重要的一部分,在H5尚未流行起来的时候,jQuery为咱们提供了一个简洁,方便,高效的DOM操做模式,成为那个时代的经典。虽然如今Vue,React等MVVM框架的热度如日中天,可是了解下jQuery的DOM选择器设计思路,能够学习到Sizzle设计的精妙之处,为本身模块设计和框架设计提供很好的参考意义,也为了解MVVM框架虚拟DOM打下更好的基础。数组
二.Sizzle的特别之处浏览器
首先介绍下jQuery选择器模块,就是Sizzle选择器,他的网址是http://sizzlejs.com/,若是你只须要进行文档节点的查询,能够直接引入Sizzle的文件就能够了,而不须要整个jQuery文件。
Sizzle选择器有哪些特色呢?数据结构
1. 高效,Sizzle经过不少方法来实现了极致的访问速度,为咱们搜索DOM节点提供了一个很好的指导,号称是当时最快的DOM选择器引擎。app
2. 支持多种查询方式,包括基本选择器(ID,Class,TAG),层级选择器,伪类选择器等等,符合多种复杂场景。框架
3. 体积小,压缩后只有3Kdom
Sizzle快具体在哪些缘由呢,主要从几个角度来分析函数
1. 优先浏览器本地API:好比基本选择器最终调用的是getElementById等等,对于复杂选择器若是支持querySelector接口,优先使用querySelector来查询。最后对比较老旧的选择器才使用本身的查询逻辑。那使用浏览器本地API比JS本地执行性能高出不少,不在一个数量级。性能
2. 优化选择符:经过两个角度来优化,一是尽可能缩小DOM根节点,缩小搜索的范围,另外是寻找备选种子集合,经过本地接口过滤出备选种子集合,而不是去搜索全部的DOM节点学习
3. 经过从右向左的方式来解析,在大多数状况下效率高出从左向右的模式不少
4. 经过建立编译函数,经过空间换时间的方式,来提升相同选择符的查询性能,每一个选择符查询以后都会被词法分析,而后建立为过滤函数,只要对种子集合执行过滤函数便可。
在介绍Sizzle源码以前,先解释一下从右向左分析的思路,好比有个选择符#div[name=wrapper] div[name=ad2] 若是是咱们来分析这个字符串应该怎么分析?咱们有两个选择
从左到右分析 和 从右到左分析,那么哪一个方案更优呢?答案是从右向左,即便是浏览器渲染CSS也一般是这个规则,为啥呢?
咱们考虑下HTML的基本结构,HTML被浏览器首先解析为DOM树相似于下面的结构:
假如咱们要查询ad2这个div,$("#div[name=wrapper] div[name=ad2]")
1. 按从左往右的思路,咱们首先要找到全部的Div,而后对每一个Div是否是warpper,找到之后再对比他的子节点,看看他是否是ad2,对于一个嵌套很深的DOM树来讲,每一个Div可能存在不少子节点,那么每次遍历子节点的过程将会很是耗时,这是由于父与子的关系是一对多的关系。
2. 按从右向左的思路,咱们首先找到全部的DIV,而后看看这个DIV是否是ad2,若是是的话再往上一层父节点查看,是否是wrapper,由于每一个节点只有一个父节点,那么这个查询过程瞬间讯速了不少,是否是,由于子于父的关系是多对一,咱们知道了子,那就等因而1对1,因此这个过程查询的几率效率确定要比从左向右迅速许多。
三.如何分析框架源码
Sizzle.js的源码总共有2000多行,里面包含了不少的正则表达式,函数和兼容性处理,咋一看头都是懵的,这里我以为读框架的源码须要有两个思路:
1. 简化模块,把主线留下:
首先把源码分层,好比jQuery的事件和委托机制,以前文章中介绍过,总共分了4,5层,这样一层一层的分析,能够由底向上,集中注意力,一点点解开源码的大门,不然各类模块耦合在一块儿会让你看的怀疑人生。
2. 理清思路,找出设计图纸
了解做者的思路,咱们每一个人在编码的时候是有一个设计流程或者设计图,还有数据结构,咱们首先就要经过注释或者相关资料了解做者的这些思路,能够很快的读通源码流程,而不是一上来就淹没在源码中,效率很低。
四.Sizzle框架设计思路分析
首先那咱们在分析Sizzle的时候就首先作一个分层处理:
第一层 把兼容性相关逻辑去掉,只保留最多见的选择符的流程,咱们假设咱们的浏览器都是没有bug的,只须要走正常流程。
第二层 咱们把比较复杂的位置伪类相关的逻辑去掉,只考虑普通选择符和层级选择符,好比 $("#div_test > span input[checked=true]"),先不考虑相似:first等位置伪类,这样,源码一会儿就精简了不少,等分析完了再加上去掉的逻辑。
第二,咱们须要把Sizzle查询的总体思路给画出来,把做者的设计思路画出来,再分析源码就清晰不少。
而后咱们来了解一下Sizzle的整个流程图:
首先浏览器先作兼容性和初始化的一些处理,这些略过,而后经过正则表达式判断当前的选择符是否是 ID或者Class或者Tag的简单表达式,若是是的话直接调用JS原生接口getElementById/getElementsByClassName/getElementsByTagName来查询结果,这种效率是最高的,由于JS原生API是性能最好的。
若是是复杂选择器,好比带层级关系或者带伪类等,再判断浏览器是否是支持querySelectorAll高级查询,若是支持,调用querySelectorAll便可,这也是性能比较高的方案,可是若是咱们的浏览器版本比较低不支持的话,就只能走下面Sizzle本身的方式来了。
因而可知,随着ES标准的发展,jQuery也引入了最新的API,从而实现了性能的最大优化。
如今进入到Sizzle本身的逻辑来了,首先进入select函数,看看整个流程,好比对于#div_test > span input[checked=true]
1. 首先进入词法分析过程tokenize把选择符字符串转换为token数组,以便后面分析使用,具体过程咱们后面再说
2. 尝试缩小上下文范围,默认上下文是document,在这里咱们发现#div_test是个ID选择符,能够直接把上下文定位到div_test这个节点,从而提升了查询性能
3. 尝试寻找一个初始集合seed,也就是说缩小备选dom列表,这里是input,因此咱们把div_test节点下的全部子节点中的input节点做为seed数组保存起来
4. 将剩下的选择符进行编译保存,而后执行编译函数获得结果。
这里有几个细节说明一下,tokenize函数实现的过程是不少编译器实现的一种方式,好比js代码在执行以前也是从字符串须要进行词法分析,编译优化再执行的过程,经过tokenize可让机器能理解咱们的数据。Sizzle也经过两个尝试,一是缩小上下文,一是创建初始集合seed集合,从而尽量的去缩小查询的范围,尽量的提升查询的性能。