经过了前六篇文章的分析, 大致了解了jQuery的总体架构, 今天就来看一下非常重要的知识点————Sizzle
一、更新
[2019-4-21]
Changed
- 改进文章排版格式
二、前置
2.1 正则
由于只分析几种常用的选择器的实现原理, 所以需要点正则能力, 起码要对*、.、[]、分组匹配、?:x等…了如指掌.
三、是什么?
3.1 querySelector&querySelectorAll
要了解Sizzle的存在意义以及原理, 首先得了解querySelector和querySelectorAll这两个API.
Q: 什么是
querySelector&querySelectorAll?
A: CSS3新推出的API.
Q: 它有什么用?
A: 用来替代传统的document.getElementsByTagName、document.getElementById方法, 来执行更复杂的DOM选取操作.
Q: 如何使用其选取?
A: 两种用途(以querySelector为例)
Q: 它的兼容性如何?
A: 截取了MDN上对其兼容性的描述, 如下图:

2.1.1 基本选择器
PS: 较常用, 我基本只用到过这几个😄
1 | document.querySelector('#app'); |
2.1.2 CSS3选择器
PS: 针对
CSS3新推出的较为复杂、高级的选择器.
1 | document.querySelector('p.text span:nth-of-type(2)'); |
3.2 Sizzle
上面提到了关于querySelector的兼容性问题, 其实, 这就是Sizzle的诞生原因:
- 解决低版本IE(
IE8)不支持querySelector的情况
它本质上就是一个**polyfill**, 那么:
Q: 何所谓polyfill?
我的理解是:
- 实现了某个浏览器不支持的API
这样一来, 问题就清晰多了, 其实:
Sizzle实现的功能和document.querySelector是一样的.
四、为什么?
Q: 为什么jQuery要搞一个
Sizzle?
jQuery将处理选择器的功能交给了Sizzle, 也是为了抽取核心逻辑, 目前Sizzle已经是一个单独的模块, 可以供开发者使用了.
五、怎么做?
5.1 准备工作
源码中有关Sizzle的部分是一个自执行函数, 所以将它单独拎出来到外部文件, 大概两千多行的样子.
由于能力有限, 所以, 只会简单了解一下其大致的运作原理即可.
5.2 如何使用
正式开始之前, 先来了解一下Sizzle是如何使用的, 由于jQuery已经将其抽离出了一个单独的库, 所以直接引入测试项目集就行了.
在浏览器中可以看到:

可以发现, Sizzle返回了匹配到的DOM结果集.
5.3 大致结构
了解了Sizzle.js的基础用法, 接下来就是看源码了.
我将其源码分为了以下几部分:
PS: 标出的位置均位于
origin/Sizzle.js中, 文件地址
1 | /** |
划分好了基本结构, 那么接下来就照着它来进行下一步——**简单分析**.
PS: 力荐
VS Code插件——**Bookmarks**, 源码阅读必备!
- 市场截图:

- 使用截图

5.4 简单分析
5.4.1 正则组合
总览: 定义一系列针对
CSS2、CSS3选择器规范的标准正则, 组装为正则表达式组, 塞入matchExpr对象.
先来梳理几个问题:
Q: 何为
标准正则?
A: 这只是我取的名字, 因为其根据CSS选择器规范来指定.
Q:
组装的过程?
A: 本质就是字符串的拼接.
Q: CSS选择器的种类?
A: id选择器(#)、类选择器(.)、标签选择器(tag)、属性选择器(input[type=""])、伪类选择器(:hover、:nth)、组合选择器(+、~、>、' ')
接着, 继续往下看:
PS: 由于代码太多, 因此我挑选了几个贴出来
1 | // 匹配空白字符 |
可以看到, 终究是噩梦般的正则, 特别是ATTR, 抄都抄错了…
上述几个正则都可以从字面意思上看出它们的用途, 干巴巴的说可能很枯燥, 所以还是去console试一下比较好
PS: 我这里就不贴
测试正则的截图了, 可以自行尝试, 毕竟与后续的编译相挂钩.
总结一下, jQuery通过定义一系列匹配CSS选择器类型的正则, 将诸如:
1 | const selector = '#app .post span.text > input[type="text"]'; |
此类的复杂选择器拆分为小的chunk:
- ‘#app’
- ‘ ’
- ‘.post’
- ‘ ’
- ‘span.text’
- ‘>’
- ‘input[type = “text”]‘
对于chunk的操作, 后续会说到.
5.4.2 Sizzle主函数
PS: 满怀期待的守在电脑前, 准备欣赏
海军70周年庆, 结果…听说是由于大雾取消了直播, 很可惜.
PS: 但是文章不能不写, 因为今天的任务还未完成, 趁着有精力赶紧把
Sizzle主函数相关的内容写一下.
5.4.2.1 debugger调试
PS: 对于经常阅读源码的码农来说, 灵活的应用
DevTools是至关重要的, 对于简单的库, 可以追踪变量引用、函数堆栈信息, 比起傻傻的翻源码方便多了(但是对于大型框架并不适用).
这里简单演示一下:

5.4.2.2 内部逻辑
照例, 先贴出一部分比较重要的源码:
1 | function Sizzle(selector, context, results, seed) { |
上述就是我抽取出来的部分逻辑代码, 可以看到, 其内部主要进行了以下几种操作:
- 非法值过滤
- 简单选择器匹配
- querySelectorAll检测
- select主操作函数
可以用一句话概括其处理逻辑:
能用浏览器原生API, 就不用Sizzle了
5.4.2.3 两个疑问
Q: 源码中的
nonnativeSelectorCache函数是什么?
A: 对应createCache闭包函数, 为Sizzle的缓存机制, 将selector以Key-value-pair的形式存储于createCache内部维护的keys数组中.
PS:
createCache源码可参考这里
Q: 源码中
select方法起何种作用?
A: 下一区块再议
5.4.3 select主操作函数
5.4.4 filters过滤器
5.4.5 tokenize令牌化
5.4.6 compile编译器
六、我学到了
6.1 有用的正则
6.1.1 匹配空白字符
1 | const rwhitespace = /[\x20\t\r\n\f]+/g; |
6.1.2 匹配简单选择器
PS: 形如
#app、.text、span等单选择器.
1 | const rsimpleselector = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/; |
七、示例代码
示例代码参考仓库地址