jQuery原始碼分析之select()方法

antzone發表於2017-04-03

本章節對jQuery原始碼中的select()實現進行一下分析。

分析文字在原始碼中,以註釋的形式存在,感興趣的朋友可以做一下參考。

程式碼如下:

[JavaScript] 純文字檢視 複製程式碼
/*
 * select方法是Sizzle選擇器包的核心方法之一,其主要完成下列任務:
 * 1、呼叫tokenize方法完成對選擇器的解析
 * 2、對於沒有初始集合(即seed沒有賦值)且是單一塊選擇器(即選擇器字串中沒有逗號),
 *  完成下列事項:
 *  1) 對於首選擇器是ID型別且context是document的,則直接獲取物件替代傳入的context物件
 *  2) 若選擇器是單一選擇器,且是id、class、tag型別的,則直接獲取並返回匹配的DOM元素
 *  3) 獲取最後一個id、class、tag型別選擇器的匹配DOM元素賦值給初始集合(即seed變數)
 * 3、通過呼叫compile方法獲取“預編譯”程式碼並執行,獲取並返回匹配的DOM元素
 * 
 * @param selector 已去掉頭尾空白的選擇器字串
 * @param context 執行匹配的最初的上下文(即DOM元素集合)。若context沒有賦值,則取document。
 * @param results 已匹配出的部分最終結果。若results沒有賦值,則賦予空陣列。
 * @param seed 初始集合
 */
function select(selector, context, results, seed) {
      
    var i, tokens, token, type, find, 
    // 呼叫tokenize函式解析selector
    match = tokenize(selector);
  
    // 若沒有提供初始集合
    if (!seed) {
        // Try to minimize operations if there is only one group
        // 若只有一組選擇器,即選擇器字串沒有逗號
        if (match.length === 1) {
            // Take a shortcut and set the context if the root selector
            // is an ID
            /*
             * 下面程式碼是用來處理根選擇器是ID型別的快捷方式
             * 
             * 在此使用slice[0]來建立一個新的集合,
             * 確保原有的集合不會被之後程式碼變更掉
             */
            tokens = match[0] = match[0].slice(0);
            /*
             * 若選擇器是以id型別開始,且第二個是關係符(即+~>或空格),
             * 則獲取id所屬物件作為context繼續完成後續的匹配
             * 
             * 此處的條件判斷依次為:
             * tokens.length > 2 :若tokens有兩個以上的選擇器
             * (token = tokens[0]).type === "ID" :第一個選擇器的型別為ID(即以#開頭的),
             * support.getById :支援getElementById函式
             * context.nodeType === 9 :context物件是document
             * documentIsHTML :當前處理的是HTML程式碼
             * Expr.relative[tokens[1].type] :第二個tokens元素是一個關係(即+~>或空格)
             * 在滿足上面所有條件的情況下,執行if內的語句體
             */
            if (tokens.length > 2 && (token = tokens[0]).type === "ID"
                    && support.getById && context.nodeType === 9
                    && documentIsHTML && Expr.relative[tokens[1].type]) {
  
                // 將當前上下文指向第一個ID選擇器指定的節點物件
                context = (Expr.find["ID"](token.matches[0].replace(
                        runescape, funescape), context) || [])[0];
                  
                // 若當前上下文內沒有指定ID物件,則直接返回results
                if (!context) {
                    return results;
                }
                  
                // 選擇器字串去掉第一個ID選擇器
                selector = selector.slice(tokens.shift().value.length);
            }
  
            // Fetch a seed set for right-to-left matching
            /* 
             * 下面while迴圈的作用是用來根據最後一個id、class、tag型別的選擇器獲取初始集合
             * 舉個簡單例子:若選擇器是"div[title='2']",
             * 程式碼根據div獲取出所有的context下的div節點,並把這個集合賦給seed變數,
             * 然後在呼叫compile函式,產生預編譯程式碼,
             * 預編譯程式碼完成在上述初始集合中執行[title='2']的匹配
             * 
             * 首先,檢查選擇器字串中是否存在與needsContext正規表示式相匹配的字元
             * 若沒有,則將依據選擇器從右到左過濾DOM節點
             * 否則,將先生成預編譯程式碼後執行(呼叫compile方法)。 
             */
              
            /*
             * "needsContext" : new RegExp("^" + whitespace
             *      + "*[>+~]|<img src="static/image/smiley/default/sad.gif" smilieid="2" border="0" alt="">even|odd|eq|gt|lt|nth|first|last)(?:\\("
             *      + whitespace + "*((?:-\\d)?\\d*)" + whitespace
             *      + "*\\)|)(?=[^-]|$)", "i")
             * needsContext用來匹配選擇器字串中是否包含下列內容:
             * 1、>+~三種關係符
             * 2、:even、<img src="static/image/smiley/default/shocked.gif" smilieid="6" border="0" alt="">dd、:eq、:gt、:lt、:nth、:first、:last八種偽類
             * 其中,(?=[^-]|$)用來過濾掉類似於:first-child等帶中槓的且以上述八個單詞開頭的其它選擇器
             */
            i = matchExpr["needsContext"].test(selector) ? 0
                    : tokens.length;
            while (i--) {
                token = tokens[i];
  
                // Abort if we hit a combinator
                // 遇到關係符跳出迴圈
                if (Expr.relative[(type = token.type)]) {
                    break;
                }
                if ((find = Expr.find[type])) {
                    // Search, expanding context for leading sibling
                    // combinators
                    /*
                     * rsibling = new RegExp(whitespace + "*[+~]")
                     * rsibling用於判定token選擇器是否是兄弟關係符
                     */
                    if ((seed = find(token.matches[0].replace(
                            runescape, funescape), rsibling
                            .test(tokens[0].type)
                            && context.parentNode || context))) {
  
                        // If seed is empty or no tokens remain, we can
                        // return early
                        // 剔除剛用過的選擇器
                        tokens.splice(i, 1);
                        selector = seed.length && toSelector(tokens);
                        /*
                         * 若selector為空,說明選擇器僅為單一id、class、tag型別的,
                         * 故直接返回獲取的結果,否則,在獲取seed的基礎上繼續匹配
                         */
                        if (!selector) {
                            push.apply(results, seed);
                            return results;
                        }
  
                        break;
                    }
                }
            }
        }
    }
  
    // Compile and execute a filtering function
    // Provide `match` to avoid retokenization if we modified the
    // selector above
    /*
     * 先執行compile(selector, match),它會返回一個“預編譯”函式,
     * 然後呼叫該函式獲取最後匹配結果
     */
    compile(selector, match)(seed, context, !documentIsHTML, results,
            rsibling.test(selector));
    return results;
}

相關文章