Sizzle 源码分析讨论(一): 正则表达式

前言

这是我第一篇文章,最近看司徒大大的《javascript框架设计》,看到了Sizzle这里,就想看看源码,上网找了文章版本都是1.7以前的,并且很零零散散。因此打算本身写一些文章,从头至尾把Sizzle读一遍。做为一个前端小白,确定是有不少看不懂的,但愿各位大佬能吹风磁暴帮帮我。我也在GitHub以注释的方式分析源码: 施工中。OK, 不废话了开始干。javascript


正则表达式

正则分两种写法,一种是字面量;一种是用RegExp构造函数。当时用构造函数模式的时,所传入的字符串须要解析, 如:html

//这是字面量
var reg1 = /\\/;
//这是构造函数
var reg2 = new RegExp('\\\\');
复制代码

上面所示的两个正则是匹配的字符是相同的都是匹配一个\, 可是传入构造函数的字符串要4个\,缘由是在传入构造函数时,须要解析一次。这里能够去看红宝书的正则那一章写的很清楚。使用这种解析模式的正则读起来会特别的麻烦。Sizzle用的就是这种解析。前端


Sizzle的正则

Sizze中的正则主要是为了匹配选择器,区分是什么类型的选择器,进行分类。它拆分了几个基础的正则字符串,并再最后进行组合拼接,而后经过构造函数,new出实例。
java

因为Sizzle的正则都是构造函数模式的,我会在它的代码下面注释一个字面量模式的方便阅读。git


booleans
先从最简单的开始,booleans就是一堆或,这里应该是进一步匹配没有值的伪类。好比::checked :disabled,而不去匹配带值的,好比::nth-child(3)github

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
async

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匹配四种状况。ide

  1. \\[\da-fA-F]{1,6}[\x20\t\r\n\f]?匹配的是 ASCII表。如\61, 之因此是{1,6},应该支持二进制。
  2. \\[^\r\n\f]匹配的是\加不是\r\n\f的任意可见或不可见字符, 如\a
  3. [\w-]匹配的是任意可见字符或-
  4. [^\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']

生成正则实例

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 括号中所有的内容 // $4 even odd 或者 表达式2n+1 中的2n // $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所有的正则

相关文章
相关标签/搜索