sizzle分析记录:词法分析器(tokenize)

时间:2022-09-02 05:04:00

词法分析器(tokenize)?

词法分析器又称扫描器。词法分析是指将我们编写的文本代码流解析为一个一个的记号,分析得到的记号以供后续语法分析使用。

sizzle引入了tokenize这个概念,意义?

jQuery的选择器,实现了css1-css3的API,但是ECMAScript低版本的API中本身没有针对这种CSS的处理API,在IE8以上就引入了querySelectorAll

各种浏览器实现还有差异,这里不是主题,我们看如果是低版本的接口要如果处理复杂的CSS选择器

首先面临的就是要对复杂的选择器进行分解

例如:

div > div.Aaron p span.red

只能针对每个版本的浏览器的支持力度去匹配各自的选择

所以此时会引入一个词法分析器(tokenize)用来把用户复杂的匹配选择,分解成各自的单元,可以提供给后面对应的接口处理


选择器总的来说分四大类:

并联就是合并分组,用逗号分隔

简单的选择器,ID,TAG,CLASS,ATTR,*

关系选择器:> ,+ , ~, 空格

伪类:动作伪类,目标伪类,语言伪类,状态伪类,结构伪类,取反伪类

Sizzle的Token格式如下 :

{
value:'匹配到的字符串',
type:'对应的Token类型',
matches:'正则匹配到的一个结构' }

tokenize需要解析的几种情况:

情况一:多重选择器分组

soFar :$("div, span, p.myClass" )

在出现逗号分隔符的时候,就说明选择所有指定的选择器的组合结果,所以需要分割成各自的处理模块

这种事情当然交给正则来干是最合适的

常规的思路先是通过split(,)先把选择器劈成三部分,然后依次处理各自的模块

jQuery对于过滤正则都有一个特点,就是都是元字符^开头,开限制匹配的初始,所以tokenize也是从左边开始一层一层的剥离

rcomma.exec( soFar )) 

var whitespace = "[\\x20\\t\\r\\n\\f]";
var rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" )

匹配第一个出现的非显示字符

换句话匹配

, span, p.myClass

然后在划分容器

if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[0].length ) || soFar;
}
groups.push( (tokens = []) );
}

其结果就是:(结构不合理,先理解这个意思)

groups:[
tokens :{div, span},
tokens :{p.myClass}
]

情况二:关系处理器分组

在层级关系中有几种特殊的划分 Token : >, +, 空格, ~ 用来表明父与子,兄弟,祖辈子孙之间的层级关系

$( "ul.topnav > li" )

从 > 划分

rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

可以是>+~或者空白

这个分组是为了之后的关系选择确定

if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push({
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
});
soFar = soFar.slice( matched.length );
}

剩余几种Token :

Expr.filter :TAG, ID, CLASS, ATTR, CHILD, PSEUDO

通过一系列的正则抽出表达式中的内容

ID:

///^#((?:\\.|[\w-] | [^\x00-\xa0] ) +)/
var characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+";
var ID = new RegExp("^#(" + characterEncoding + ")")
console.log(ID.exec("#div > li")) //["#div", "div", index: 0, input: "#div > li"]

TAG:

var TAG =  new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" );
console.log(TAG.exec("li > sadf.da dsf")) //["#div", "div", index: 0, input: "#div > li"]

CLASS:

var Class = new RegExp( "^\\.(" + characterEncoding + ")" );
console.log(Class.exec(".li > sadf.da dsf"))

ATTR:

属性选择器有点复杂,通过第一次正则只能匹配器出整体,所以需要第二次分解,引入了Expr.preFilter

Expr.preFilter保留了3个兼容处理分别是ATTR,CHILD,PSEUDO复杂的选择器

var identifier = characterEncoding.replace( "w", "w#" );
var attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
"*\\]"; var ATTR = new RegExp( "^" + attributes ); console.log(ATTR.exec("[name*='man']")

preFilter:

preFilter: {
"ATTR": function( match ) {
match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) {
match[3] = " " + match[3] + " ";
} return match.slice( 0, 4 );
},

伪类放下一章