前言
这是我第一篇文章,最近看司徒大大的《javascript框架设计》,看到了
Sizzle这里,就想看看源码,上网找了文章版本都是1.7之前的,而且很零零散散。所以打算自己写一些文章,从头到尾把Sizzle读一遍。作为一个前端小白,肯定是有很多看不懂的,希望各位大佬能吹风磁暴帮帮我。我也在GitHub以注释的方式分析源码: 施工中。OK, 不废话了开始干。
正则表达式
正则分两种写法,一种是字面量;一种是用RegExp构造函数。当时用构造函数模式的时,所传入的字符串需要解析, 如:
//这是字面量
var reg1 = /\\/;
//这是构造函数
var reg2 = new RegExp('\\\\');
上面所示的两个正则是匹配的字符是相同的都是匹配一个\, 但是传入构造函数的字符串要4个\,原因是在传入构造函数时,需要解析一次。这里可以去看红宝书的正则那一章写的很清楚。使用这种解析模式的正则读起来会特别的麻烦。Sizzle用的就是这种解析。
Sizzle的正则
Sizze中的正则主要是为了匹配选择器,区分是什么类型的选择器,进行分类。它拆分了几个基础的正则字符串,并再最后进行组合拼接,然后通过构造函数,new出实例。
由于Sizzle的正则都是构造函数模式的,我会在它的代码下面注释一个字面量模式的方便阅读。
booleans
先从最简单的开始,booleans就是一堆或,这里应该是进一步匹配没有值的伪类。比如::checked :disabled,而不去匹配带值的,比如::nth-child(3)。
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
"ismap|loop|multiple|open|readonly|required|scoped",
whitespace(空白)
这是Sizzle自己写的空白,与\s的区别是少了一个\v。
这里为什么少一个
\v,我不太清楚,如果有知道的大佬,请告知谢谢。
whitespace = "[\\x20\\t\\r\\n\\f]"
//字面量
copy_whilespace = '/[\x20\t\r\n\f]/'
- \x20 表示ASCII 十六进制第20个字符 也就是空白
- \t 表示制表符
- \r 表示回车符
- \n 表示换行符
- \f 表示换页符
identifier(标识符)
这个应该算是sizzle正则表达式的核心了,它可以匹配.class中的class,可以匹配:nth-child(3)中的nth-child。
identifier = "(?:\\\\[\da-fA-F]{1,6})" + whitespace +
"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+"
//字面量
copy_identifier = '/(?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\x00-\x7f])+/'
首先identifier是一个非获取捕获,identifier匹配四种情况。
\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?匹配的是 ASCII表。如\61, 之所以是{1,6},应该支持二进制。\\[^\r\n\f]匹配的是\加不是\r\n\f的任意可见或不可见字符, 如\a。[\w-]匹配的是任意可见字符或-。[^\x00-\x7f]匹配不是ASCII表中的所有字符
这意味着,像诸如#.()~=!$等等这些是不会被匹配到的。
attributes(属性)
attributes匹配的是属性选择器,可以匹配[attr], 可以匹配[attr $= 'val'],但是不能匹配[attr =],也不能匹配[attr.]
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
"*([*^$|!~]?=)" + whitespace +
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
whitespace + "*\\]";
//字面量 由于把identifier和whitespace都替换成正则的话 实在是太长了 这里直接用变量名代替了
//为了好阅读, 换点行
copy_attributes = `\[whitespace*(identifier)
(?:whitespace*([*^$|!~]?=)whitesapce*
(?:
'((?:\\.|[^\\'])*)'|
"((?:\\.|[^\\\"])*)"|
(identifier)
)
|)whitespace*\]`;
//真正的样子
true_attributes = /\[[\x20\t\r\n\f]*((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\x00-\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-]|[^\x00-\x7f])+))|)[\x20\t\r\n\f]*\]/
属性正则匹配两种状态的属性选择器:一种有等号的[attr=val],一种没有等号的[attr]。将这两个区分开的是最外层的非捕获组的“或|“。
属性正则比较复杂,但是如果按捕获组来看的话,就很清晰了。属性正则一共有五个捕获组,除了第一个属性名一定会有值之外,其余的捕获组需要满足一定的条件:
- $1
(identifier): 属性名。 - $2
([*^$|!~]?=): 运算符。如果是[attr]这种情况的话,值为空 - $3
'((?:\\.|[^\\'])*)':用单引号括起来的属性值。[attr='val']的val - $4
"((?:\\.|[^\\\"])*)":用双引号括起来的属性值。[attr="val"]的val - $5
(identifier):没有用引号括起来的值。[attr=val]的val
引号中匹配的值和没有引号匹配的值也是有区别的。如[class=".aa"]可以匹配,但是[class=.aa]就不可以。
pseudos(伪类)
pseudos匹配的是伪类选择器
pseudos = ":(" + identifier + ")(?:\\((" +
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
".*" +
")\\)|)";
//字面量
copy_pseudos = `:(identifier)
(?:\((
('((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)")|
((?:\\.|[^\\()[\]]|attributes)*)|
.*
)\)|)`
//全部
true_pseudos = /:((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\x00-\x7f])+)(?:\((('((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)")|((?:\\.|[^\\()[\]]|\[[\x20\t\r\n\f]*((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^\x00-\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-]|[^\x00-\x7f])+))|)[\x20\t\r\n\f]*\])*)|.*)\)|)/
伪类也有两种情况,一种:after,另一种是:nth-child(3)。伪类有11个捕获组,但是$7-$11都是attributes的。
这里只看前6个捕获组:
$1(identifier):伪类名。如:nth-child(3)中的nth-child,:after中的after。$2\((....)\):最外层的捕获组, 捕获括号内所有的内容。如:nth-child(3)中的3;:nth-child("3")中的"3";:nth-child('3')中的'3';:not(".className")中的".className"。$3('((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)"):小括号内使用引号的字符串。如:nth-child("3")中的"3";:nth-child('3')中的'3'。$4((?:\\.|[^\\'])*):单引号中的值。如:nth-child('3')中的3。$5((?:\\.|[^\\"])*):双引号中的值。如:nth-child("3")中的3。$6((?:\\.|[^\\()[\]]|attributes)*):除了()[]\的字符串或者属性。如:nth-child(3)中的3;:not([href = 'aaa'])中的[href = 'aaa']。
通常情况下$3和$6,这两个捕获组其中之一都必然有值。
但是有一种特殊情况:,:not(:nth-child(3)),这种情况下$3和$6都是没有值的。Sizzle通过$6是否有值,区分了一些场景
生成正则实例
var rwhitespace = new RegExp( whitespace + "+", "g" ), //空白
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), //前后留白
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), //逗号
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), //组合器
rdescend = new RegExp( whitespace + "|>" ), //后代
rpseudo = new RegExp( pseudos ), //伪类
ridentifier = new RegExp( "^" + identifier + "$" ), //标识码
matchExpr = {
"ID": new RegExp( "^#(" + identifier + ")" ),
"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
"TAG": new RegExp( "^(" + identifier + "|[*])" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
/**
/^:(only|first|last|nth|nth-last)-(child|of-type)
(?:\(whitespace*(
even|odd|(([+-]|)(\d*)n|)whitespace*
(?:([+-]|)whitespace*(\d+)|)
)whitespace*\)|)/i
// $1 only|first|last|nth|nth-last
// $2 child|of-type
// $3 括号中全部的内容 如 2n+1、2n、1、even、odd...
// $4 表达式2n+1 中的2n 如果为even 或者 odd的话 这里为undefined
// $5 2n的正负 +2n -2n 中的 + -
// $6 n的倍数 2n 中的 2
// $7 运算符 + -
// $8 最后一个数 1
*/
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
"needsContext": new RegExp( "^" + whitespace +
"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
rhtml = /HTML$/i,
rinputs = /^(?:input|select|textarea|button)$/i,
rheader = /^h\d$/i, //h1 h2 h3 h4 h5 h6..
rnative = /^[^{]+\{\s*\[native \w/,
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, //快速寻找ID CLASS 标签
rsibling = /[+~]/; //兄弟
以上就是Sizzle全部的正则