這篇文章主要介紹了jQuery選擇器源碼解讀(三):tokenize方法,本文用詳細的注釋解讀了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),則返回緩存結果,
- // 若不是,則返回0
- return 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)返回空字符串,
- * 故最終返回的是||后面的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類型的正則表達式對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數組,同時將它賦予matched
- matched = 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 tokens
- tokenCache(selector, groups).slice(0);
- }
新聞熱點
疑難解答
圖片精選