jQuery原始碼分析之tokenize()方法
tokenize方法是選擇器解析的核心函式,下面就是對jQuery原始碼中它的實現過程。
程式碼例項如下:
[JavaScript] 純文字檢視 複製程式碼/* * 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); }
相關文章
- jQuery原始碼分析jQuery原始碼
- jQuery2.0.3原始碼分析jQuery原始碼
- jQuery原始碼解析之position()jQuery原始碼
- jQuery原始碼解析之clone()jQuery原始碼
- jQuery原始碼學習之$()jQuery原始碼
- jQuery原始碼解析之replaceWith()/unwrap()jQuery原始碼
- jQuery原始碼學習之eventjQuery原始碼
- jQuery原始碼學習之extendjQuery原始碼
- Vue-Router原始碼分析之install方法Vue原始碼
- jQuery原始碼剖析(三) - Callbacks 原理分析jQuery原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- jQuery原始碼分析系列 : 整體架構jQuery原始碼架構
- ArrayList方法原始碼分析原始碼
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之 LinkedList原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- jquery中on方法原理分析jQuery
- Spring AOP之原始碼分析Spring原始碼
- DRF之Response原始碼分析原始碼
- Redux原始碼分析之createStoreRedux原始碼
- MongoDB原始碼分析之MongosXFMongoDB原始碼
- BlueStore原始碼分析之FreelistManager原始碼
- JUC之ReentrantLock原始碼分析ReentrantLock原始碼
- JUC之CountDownLatch原始碼分析CountDownLatch原始碼
- Flutter原始碼分析之InheritedWidgetFlutter原始碼
- lodash原始碼分析之isArguments原始碼
- Envoy原始碼分析之Dispatcher原始碼
- Fresco原始碼分析之DraweeView原始碼View
- Netty原始碼分析之LengthFieldBasedFrameDecoderNetty原始碼
- RecyclerView之SnapHelper原始碼分析View原始碼
- tornado 原始碼之 coroutine 分析原始碼
- lodash原始碼分析之isObjectLike原始碼Object
- Fresco原始碼分析之Hierarchy原始碼
- Dubbo之SPI原始碼分析原始碼
- OpenGL 之 GPUImage 原始碼分析GPUUI原始碼