/* * 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 * + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" * + whitespace + "*((?:-\\d)?\\d*)" + whitespace * + "*\\)|)(?=[^-]|$)", "i") * needsContext用來匹配選擇器字符串中是否包含下列內容: * 1、>+~三種關系符 * 2、:even、:odd、: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; }