简述
工欲善其事,必先利其器,想看懂源码我们就从小开始逐步分析,希望每个人看了以后都能看懂jquery源码,首先我选择的是分析sizzle源码,因为我们使用jquery就需要知道,jquery是怎么通过css选择器来选中元素的。所以 分析源码之前需要了解一下css选择器有哪些,我总结分为了以下几种类型。 如果有问题或者理解上的一些错误。欢迎大家指正。
选择器简单分类
简单选择器
- ID选择器
- 类型选择器
- 标签选择器
- *
组合选择器
- > 子组合选择器
-
- 相邻兄弟选择器 (当前元素的的单个元素)
- ~ 相邻兄弟选择器(当前元素后的所有元素)
- ,分组选择器
- 空格 后代选择器(后代)
- ||列组合器
伪类选择器
一般“:”开头的是伪类选择器
1.伪类选择器中有子类选择器1. ^:(only|first|last|nth|nth-last)-(child|of-type)
2.nth-child (n) :n可以是odd event 或者是n 或者是2n+1 或者2n-1 可能是负数
伪元素选择器
一般“::”开头的是伪类选择器
属性选择器
一般是以为“[]” 包裹的,例如[title]
一、 sizzle是如何来识别css选择器的。
首先清楚基本的原理,再向下继续研究大家就会看得更加明白,有些地方就会豁然开朗了。这里我会把sizzle中选择器中的new RegExp()创建的选择器,转为// 来分析,然后进行整个源码的剖析。
whitespace
- whitespace = "[\x20\t\r\n\f]" \x20 16进制,转换为二进制0010 0000 转为10进制 32 ,在acsii中对应是空格 ,这里有一个小知识点,sizzle使用构造函数创造正则对象时,需要常规的字符转义规则(在前面加反斜杠 \)例如:
var reg = new RegExp("\\w") 与var reg = /\w/
是等价的。这里我们转成普通的大家看时会稍微简单明了一些
whitespace = /[\x20\t\r\n\f]/
\x20 换行 \t制表符 \r回车 \n 换行 \f换页符
identifier
identifier = "(?:\\[\da-fA-F]{1,6}" + whitespace + "?|\\[^\r\n\f]|[\w-]|[^\0-\x7f])+" www.w3.org/TR/css-synt… 这个是w3c描述css词法解析大家有兴趣可以看一下。
var identifier = /(?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\0-\x7f])+/
?: 不对当前分组进行捕获
开始匹配\da-fA-F{1,6} 就是匹配数字加字符a-fA-F中的1到6个。\\000023 \\0000FF表示匹配16进制的unicode码位
或则[\x20\t\r\n\f]? 0 到1次空格
或者[^\r\n\f] 不是这些控制符
或者是\w-
或者[^\0-\x7f] 不是0到|x7f之间的字符,以为你这些都是控制字符
字符加“-” 或者不是\0000\07xf少一个
attributes
attributes = "\[" + whitespace + "(" + identifier + ")(?:" + whitespace + "([^$|!~]?=)" + whitespace + " whitespace + "*\]" 属性选择器已经在上述做过简介,所以这里不给大家再说了,如果有不明白的地方可以去查查文档
var attributes =/\[[\x20\t\r\n\f] *((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\0-\x7f])+)(?:[\x20\t\r\n\f]* ([*^$|!~]?=) [\x20\t\r\n\f]*[\x20\t\r\n\f]+*\]/
\[[\x20\t\r\n\f] *匹配多个空白的字符
接下来就是匹配identifier 上面已经描述
(?:[\x20\t\r\n\f]* ([*^$|!~]?=) 匹配必须包含*或者^$|!~含任意的
开始分析这个复杂的正则表达式,
[] 包裹是属性选择器
空格
标识符
([*^$|!~]?=) 匹配“*” 或者“^”、“$”、“!”、“~” 正向肯定预查?=
如果遇到这些字符就先匹配前面的属性
pseudos
pseudos = ":(" + identifier + ")(?:\((" + "('((?:\\.|[^\\']))'|"((?:\\.|[^\\"]))")|" + "((?:\\.|[^\\()[\]]|" + attributes + "))|" + "." + ")\)|)"
var pseudos = /:((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)(?:\((('((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)")|((?:\\.|[^\\()[\]]|\[[\x20\t\r\n\f]*((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)(?:[\x20\t\r\n\f]*([*^$|!~]?=)[\x20\t\r\n\f]*(?:'((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)"|((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+))|)[\x20\t\r\n\f]*\])*)|.*)\)|)/
看图理解比较好
后面分析了一个与这个相似的大家可以先看后面的。
rtrim
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
whitespace + "+$", "g" )
// 匹配以空白符开始至少一到多次或者 \. 或者\\. 无限多个,继续匹配空白制符一到多个结尾,全局匹配
var rtrim = /^[\x20\t\r\n\f]+|((?:^|[^\\])(?:\\.)*)[\x20\t\r\n\f]+$/g
^[\x20\t\r\n\f]+ 匹配以空白字符至少1个或者
((?:^|[^\\])(?:\\.)*) ->(?:^|[^\\]) 这个分组匹配不捕获"^"(为字符串开始) 或者 不匹配\
->(?:\\.)* 不捕获\. 任意多次
[\x20\t\r\n\f]+$ 匹配空白符至少一次结尾
rcomma
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" )
/^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/
rcombinators
匹配组合选择器
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +"*" )
var rcombinators = /^[\x20\t\r\n\f]*([>+~]|[\x20\t\r\n\f])[\x20\t\r\n\f]*/
^[\x20\t\r\n\f]* 匹配空白符多个
([>+~]|[\x20\t\r\n\f])匹配 > + ~ 中的其中一个 或则 匹配空白符多个
[\x20\t\r\n\f]* 匹配空白符多个
// 匹配后代组合器
rdescend = new RegExp( whitespace + "|>" )
var rdescend = /[\x20\t\r\n\f]|>/
匹配空白制符或者 >
rpseudo
rpseudo = new RegExp( pseudos )
var rpseudo = /:((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)
(?:\((('((?:\\.|[^\\'])*)'|"
((?:\\.|[^\\"])*)")|
((?:\\.|[^\\()[\]]|\[[\x20\t\r\n\f]*
((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)
(?:[\x20\t\r\n\f]*([*^$|!~]?=)[\x20\t\r\n\f]*
(?:'((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)"|
((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+))|)[\x20\t\r\n\f]*\])*)|.*)\)|)/
这个整则比较复杂拆除来给大家逐个分析一下 第一组: ":" 匹配 ":" 第二组:((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+) 最外面的小括号表示分组捕获,里面的小括号是分组不捕获 \\[\da-fA-F]{1,6} 匹配css标识符 [\x20\t\r\n\f]?0到多个空白制符 或者 不是\r 软空格 \n 换行 \f 换页 或者 数字字母下划线 - 或者 不是\0-x7f之间的控制字符 1到多个 第三组:太长了不好分析我利用了 https://jex.im/regulex/ 可视化工具给转成了图方便了大家分析 大家看下图就可以了
整个正则的意思就是匹配":"css的转义字符。这里付上这块概念 可以看我另一篇文章字符集知识
第二组的意思就是匹配转义的css标识符
第三组 这里有一个:\000023 测试转移字符大家拿去试试
ridentifier
// 匹配转移的标识符
ridentifier = new RegExp( "^" + identifier + "$" )
/^(?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+$/
这个上面rpseudo已经介绍过了我就再不介绍了。
id
var id = new RegExp( "^#(" + identifier + ")" )
/^#((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)/
class
var class = new RegExp( "^\\.(" + identifier + ")" )
/^\.((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)/
TAG
var TAG = new RegExp( "^(" + identifier + "|[*])" )
/^((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+|[*])/
ATTR
var ATTR = new RegExp( "^" + attributes )
/^\[[\x20\t\r\n\f]*((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)(?:[\x20\t\r\n\f]*([*^$|!~]?=)[\x20\t\r\n\f]*(?:'((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)"|((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+))|)[\x20\t\r\n\f]*\]/
这个跟rpseudo类似
CHILD
匹配子类选择器
CHILD = new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" )
var CHILD = /^:(only|first|last|nth|nth-last)-(child|of-type)(?:\([\x20\t\r\n\f]* (even|odd|(([+-]|)(\d*)n|)[\x20\t\r\n\f]*(?:([+-]|)[\x20\t\r\n\f]*(\d+)|))[\x20\t\r\n\f]*\)|)/i
拆分出来
^: 开始
(only|first|last|nth|nth-last) 其中的一个
- 匹配连字符
(child|of-type)其中的一个
?:不捕获
\( 小括号开始
[\x20\t\r\n\f]* 匹配空白制符*个
even|odd|(([+-]|)(\d*)n|)
[\x20\t\r\n\f]*
(?:([+-]|)[\x20\t\r\n\f]*(\d+)|)
bool = new RegExp( "^(?:" + booleans + ")$", "i" )
/^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i
needsContext = new RegExp( "^" + whitespace +
"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
var needsContext = /^[\x20\t\r\n\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)(?=[^-]|$)/i
^[\x20\t\r\n\f]*[>+~]* 空白制符开始匹配*个屁> + ~中之一*个
或者:开始(even|odd|eq|gt|lt|nth|first|last)
(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)
匹配不捕获 匹配一个(开始空白制符*个,((?:-\d)?\d*)匹配不捕获-\d ?个\d *个[\x20\t\r\n\f]*,匹配),或者空白
(?=[^-]|$)匹配前面是空白制符的^ -之一的字符或者 结束
rhtml = /HTML$/i
/HTML$/i
rinputs = /^(?:input|select|textarea|button)$/i
/^(?:input|select|textarea|button)$/i
匹配是h? 比如h1
rheader = /^h\d$/i
/^h\d$/i
匹配是不是内置的js代码
rnative = /^[^{]+\{\s*\[native \w/
// 快速匹配
var rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/
#([\w-]+) 匹配#开始的字符数字下划线- 或者
(\w+)|\.([\w-]+) 分组 匹配字符1到多个或者匹配 .开始字符数字“-”一到多个
就是快速匹配id选择器或者标签选择器或者class选择器
// 快速匹配是紧邻的兄弟元素, ~ 兄弟选择器,选中兄后面所有指定第的元素
rsibling = /[+~]/