jQuery選取器代碼詳解(三)——tokenize方法
原創文章,轉載請註明出處,多謝!
/* * tokenize方法是選取器解析的核心函數,它將選取器轉換成兩級數組groups * 舉例: * 若選取器為“div.class,span”,則解析後的結果為: * group[0][0] = {type:'TAG',value:'div',matches:match} * group[0][1] = {type:'CLASS',value:'.class',matches:match} * group[1][0] = {type:'TAG',value:'span',matches:match} * 由上述結果可以看出,groups的每一個元素以逗號分隔的選取器塊的解析結果, * 另外,上述結果中的matches等於模式比對的結果,由於在此不方便寫清楚, * 故只把代碼matches:match寫在這裡。 * * tokenize方法完成如下兩個主要任務: * 1、解析選取器 * 2、將解析結果存入緩衝中,以備後用 * * * @param selector 待解析的選取器字串 * @param parseOnly 為true時,說明本次調用是匹配子選取器 * 舉個例子:若初始選取器為div:not(.class:not(:eq(4))):eq(3) * 代碼首先匹配出TAG選取器div, * 之後匹配出的pseudo選取器字串是:not(.class:not(:eq(4))):eq(3), * 代碼會把“.class:not(:eq(4))):eq(3”作為not的括弧內的值進一步進行解析, * 此時代碼在調用tokenize解析時,parseOnly參數會傳入true. */function tokenize(selector, parseOnly) {var matched, match, tokens, type, soFar, groups, preFilters, // 擷取緩衝中的結果cached = tokenCache[selector + ];/* * 若緩衝中有selector對應的解析結果 * 則執行if中語句體 */if (cached) {// 若是對初始選取器解析(parseOnly!=true),則返回緩衝結果,// 若不是,則返回0return parseOnly ? 0 : cached.slice(0);}/* * 由於字串在javascript中不是作為對象來處理的, * 所以通過賦值,代碼就自動複製了一個新字串給了soFar, * 這樣,對soFar的任何處理都不會影響selector的原有資料 */soFar = selector;groups = [];// 此處賦值,僅僅用於減少後續代碼字數,縮短執行路徑preFilters = Expr.preFilter;while (soFar) {// Comma and first run/* * rcomma = new RegExp(^ + whitespace + *, + whitespace + *) * rcomma用來判定是否存在多個選取器塊,即用逗號隔開的多個並列的選取器 * * 下麵條件判定依次為: * !matched:若是第一次執行迴圈體,則為true;否則為false。 * 這裡matched即作為是否第一次執行迴圈體的標識, * 也作為本次迴圈中soFar是否以非法字串(即非合法單一選取器)開頭的標誌。 * (match = rcomma.exec(soFar):擷取符合rcomma的匹配項 */if (!matched || (match = rcomma.exec(soFar))) {if (match) {// Don't consume trailing commas as valid/* * 剔除掉第一個逗號及之前的所有字元 * 舉個例子: * 若初始選取器為:div.news,span.closed, * 在解析過程中,首先由後續代碼解析完畢div.news,剩下,span.closed * 在迴圈體內執行到這裡時,將逗號及之前之後連續的空白(match[0])刪除掉, * 使soFar變成span.closed,繼續執行解析過程 * * 在這裡,若初始選取器的最後一個非空白字元是逗號, * 那麼執行下面代碼時soFar不變,即soFar.slice(match[0].length)返回Null 字元串, * 故最終返回的是||後面的soFar */soFar = soFar.slice(match[0].length) || soFar;}/* * 在第一次執行迴圈體或者遇到逗號分割符時,將tokens賦值為一個空數組, * 同時壓入groups數組 */groups.push(tokens = []);}matched = false;// Combinators/* * rcombinators = new RegExp( *^ + whitespace + *([>+~]| + whitespace + ) + whitespace + *), * rcombinators用來匹配四種關係符,即>+~和空白 * * 若soFar中是以關係符開始的,則執行if內的語句體 */if ((match = rcombinators.exec(soFar))) {/* * 將match[0]移除match數組,同時將它賦予matched * 若原本關係符兩邊帶有空格,則此時match[0]與matched是不相等的 * 舉個例子: * 若soFar = + .div; * 執行match = rcombinators.exec(soFar)後, * match[0] = + ,而match[1]=+; * 執行完matched = match.shift()後, * matched= + ,而match[0]=+; */matched = match.shift();// 將匹配結果壓入tokens數組中tokens.push({value : matched,// Cast descendant combinators to space/* * rtrim = new RegExp(^ + whitespace + +|((?:^|[^\\])(?:\\.)*) *+ whitespace + +$, g), * whitespace = [\x20\t\r\n\f]; * * 下面match[0].replace(rtrim, )的作用是將match[0]左右兩邊的空白替換為空白格 * 但是由於其上的match.shift的作用,match[0]已經是兩邊不帶空白的字串了, * 故此出的替換是沒有用途的代碼 */type : match[0].replace(rtrim, )});// 將關係符之後的字串賦予soFar,繼續解析soFar = soFar.slice(matched.length);}// Filters/* * 下面通過for語句對soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO類型的選取器 * 若匹配到了,則先調用該類型選取器對應的預過濾函數, * 然後,將結果壓入tokens數組,繼續本次迴圈。 */for (type in Expr.filter) {/* * match = matchExpr[type].exec(soFar):對soFar調用type類型的Regex對soFar進行匹配, * 並將匹配結果賦予match。若未匹配到資料,則match為undefined。 * !preFilters[type]:若不存在type類型的預過濾函數,則為true * match = preFilters[type](match):執行預過濾,並將結果返回給match * */if ((match = matchExpr[type].exec(soFar))&& (!preFilters[type] || (match = preFilters[type](match)))) {// 將match[0]移除match數組,同時將它賦予matchedmatched = match.shift();// 將匹配結果壓入tokens數組中tokens.push({value : matched,type : type,matches : match});// 將匹配結果之後的字串賦予soFar,繼續解析soFar = soFar.slice(matched.length);}}/* * 若matched==false, * 則說明本次迴圈沒有有效選取器(包括關係符和id、class等類型選取器) * 因此,解析到當前位置遺留下來的soFar是非法的選取器字串 * 跳出while迴圈體 */if (!matched) {break;}}// Return the length of the invalid excess// if we're just parsing// Otherwise, throw an error or return tokens/* * 若不是對初始選取器字串進行解析(!parseOnly==true), * 則返回soFar.length,此時的soFar.length代表連續有效選取器最終位置, * 後續文章將以執行個體進行說明 * 若是對初始選取器字串進行解析,則看soFar是否還有字元, * 若是,則執行Sizzle.error(selector)拋出異常; * 若不是,則執行tokenCache(selector, groups).slice(0)將結果壓入緩衝,並返回結果的副本。 */return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :// Cache the tokenstokenCache(selector, groups).slice(0);}
上一篇http://www.bkjia.com/kf/201502/377397.html