1. 程式人生 > >sizzle分析記錄:詞法分析器(tokenize)

sizzle分析記錄:詞法分析器(tokenize)

詞法分析器(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 );
    },

偽類放下一章