经过了前六
篇文章的分析, 大致了解了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-]+))$/; |
七、示例代码
示例代码参考仓库地址