摘要:歡迎來我的專欄查看系列文章。現在我們再來理一理數組,這個數組目前是一個多重數組,現在不考慮逗號的情況,暫定只有一個分支。源碼源碼之前,來看幾個正則表達式。
歡迎來我的專欄查看系列文章。
select 函數前面已經介紹了 tokensize 函數的功能,已經生成了一個 tokens 數組,而且對它的組成我們也做了介紹,下面就是介紹對這個 tokens 數組如何處理。
DOM 元素之間的連接關系大概有 > + ~ 幾種,包括空格,而 tokens 數組中是 type 是有 tag、attr 和連接符之分的,區分它們 Sizzle 也是有一套規則的,比如上一章我們所講的 Expr 對象,它真的非常重要:
Expr.relative = { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } };
Expr.relative 標記用來將連接符區分,對其種類又根據目錄進行劃分。
現在我們再來理一理 tokens 數組,這個數組目前是一個多重數組,現在不考慮逗號的情況,暫定只有一個分支。如果我們使用從右向左的匹配方式的話,div > div.seq h2 ~ p,會先得到 type 為 TAG 的 token,而對于 type 為 ~ 的 token 我們已經可以用 relative 對象來判斷,現在來介紹 Expr.find 對象:
Expr.find = {}; Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; Expr.find["TAG"] = function(){...};
實際上 jQuery 的源碼還考慮到了兼容性,這里以 find["ID"] 介紹:
if(support.getById){ Expr.find["ID"] = function(){...}; // 上面 }else{ // 兼容 IE 6、7 Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); if ( elem ) { // Verify the id attribute node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; while ( (elem = elems[i++]) ) { node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } } } return []; } }; }
可以對 find 對象進行簡化:
Expr.find = { "ID": document.getElementById, "CLASS": document.getElementsByClassName, "TAG": document.getElementsByTagName }
以后還會介紹 Expr.filter。
select 源碼源碼之前,來看幾個正則表達式。
var runescape = /([da-f]{1,6}[x20 f]?|([x20 f])|.)/gi //這個正則是用來對轉義字符特殊處理,帶個反斜杠的 token runescape.exec("ab"); //["ab", "ab", undefined] var rsibling = /[+~]/; //匹配 +、~ matchExpr["needsContext"] = /^[x20 f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:([x20 f]*((?:-d)?d*)[x20 f]*)|)(?=[^-]|$)/i //needsContext 用來匹配不完整的 selector matchExpr["needsContext"].test(" + p")//true matchExpr["needsContext"].test(":first-child p")//true //這個不完整,可能是由于抽調 #ID 導致的
而對于 runescape 正則,往往都是配合 replace 來使用:
var str = "ab"; str.replace(runescape, funescape); var funescape = function (_, escaped, escapedWhitespace) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); }
我完全看不懂啦,你們自己意會去吧,O(∩_∩)O哈哈~
var select = Sizzle.select = function (selector, context, results, seed) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize((selector = compiled.selector || selector)); results = results || []; // 長度為 1,即表示沒有逗號,Sizzle 嘗試對此情況優化 if (match.length === 1) { tokens = match[0] = match[0].slice(0); // 第一個 TAG 為一個 ID 選擇器,設置快速查找 if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { //將新 context 設置成那個 ID context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0]; if (!context) { // 第一個 ID 都找不到就直接返回 return results; // 此時 selector 為 function,應該有特殊用途 } else if (compiled) { context = context.parentNode; } selector = selector.slice(tokens.shift().value.length); } // 在沒有 CHILD 的情況,從右向左,仍然是對性能的優化 i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; while (i--) { token = tokens[i]; // 碰到 +~ 等符號先停止 if (Expr.relative[(type = token.type)]) { break; } if ((find = Expr.find[type])) { // Search, expanding context for leading sibling combinators if ((seed = find( token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context))) { // testContext 是判斷 getElementsByTagName 是否存在 // If seed is empty or no tokens remain, we can return early tokens.splice(i, 1); selector = seed.length && toSelector(tokens); //selector 為空,表示到頭,直接返回 if (!selector) { push.apply(results, seed); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above (compiled || compile(selector, match))( seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context); return results; }
toSelector 函數是將 tokens 除去已經選擇的將剩下的拼接成字符串:
function toSelector(tokens) { var i = 0, len = tokens.length, selector = ""; for (; i < len; i++) { selector += tokens[i].value; } return selector; }
在最后又多出一個 compile 函數,是 Sizzle 的編譯函數,下章講。
到目前為止,該優化的都已經優化了,selector 和 context,還有 seed,而且如果執行到 compile 函數,這幾個變量的狀態:
selector 可能已經不上最初那個,經過各種去頭去尾;
match 沒變,仍是 tokensize 的結果;
seed 事種子集合,所有等待匹配 DOM 的集合;
context 可能已經是頭(#ID);
results 沒變。
可能,你也發現了,其實 compile 是一個異步函數 compile()()。
總結select 大概干了幾件事,
將 tokenize 處理 selector 的結果賦給 match,所以 match 實為 tokens 數組;
在長度為 1,且第一個 token 為 ID 的情況下,對 context 進行優化,把 ID 匹配到的元素賦給 context;
若不含 needsContext 正則,則生成一個 seed 集合,為所有的最右 DOM 集合;
最后事 compile 函數,參數真多...
參考jQuery 2.0.3 源碼分析Sizzle引擎 - 解析原理
本文在 github 上的源碼地址,歡迎來 star。
歡迎來我的博客交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/88135.html
摘要:歡迎來我的專欄查看系列文章。我們以為例,這是一個很簡單的,逗號將表達式分成兩部分。這是針對于存在的情況,對于不存在的情況,其就是的操作,后面會談到。參考源碼分析引擎詞法解析選擇器參考手冊本文在上的源碼地址,歡迎來。 歡迎來我的專欄查看系列文章。 在編譯原理中,詞法分析是一個非常關鍵的環節,詞法分析器讀入字節流,然后根據關鍵字、標識符、標點、字符串等進行劃分,生成單詞。Sizzle 選擇...
摘要:原本是中用來當作選擇器的,后來被單獨分離出去,成為一個單獨的項目,可以直接導入到項目中使用。。本來我們使用當作選擇器,選定一些或,使用或就可以很快鎖定所在的位置,然后返回給當作對象。的優勢使用的是從右向左的選擇方式,這種方式效率更高。 歡迎來我的專欄查看系列文章。 Sizzle 原本是 jQuery 中用來當作 DOM 選擇器的,后來被 John Resig 單獨分離出去,成為一個單獨...
摘要:一種比較合理的方法就是對應每個可判斷的生成一個閉包函數,統一進行查找。根據關系編譯閉包函數,為四組編譯函數主要借助和。第四步將所有的編譯閉包函數放到一起,生成函數。 歡迎來我的專欄查看系列文章。 compile 講了這么久的 Sizzle,總感覺差了那么一口氣,對于一個 selector,我們把它生成 tokens,進行優化,優化的步驟包括去頭和生成 seed 集合。對于這些種子集合,...
摘要:源碼中接受個參數,空參數,這個會直接返回一個空的對象,。,這是一個標準且常用法,表示一個選擇器,這個選擇器通常是一個字符串,或者等,表示選擇范圍,即限定作用,可為,對象。,會把普通的對象或對象包裝在對象中。介紹完入口,就開始來看源碼。 歡迎來我的專欄查看系列文章。 init 構造器 前面一講總體架構已經介紹了 jQuery 的基本情況,這一章主要來介紹 jQuery 的入口函數 jQu...
摘要:的支持的方法有幾個主要的,和,比如官方有一個例子這兩個作為函數調用的生成從基本可以看出,函數生成了一個對象,這個對象的方法是添加回調函數,而方法則是執行回調函數。 歡迎來我的專欄查看系列文章。 講真,Sizzle 的源碼真的太壓抑了,以至于寫 Sizzle 文章的這段時間里都非常的痛苦,剛開始覺得它還挺有意思的,越到后面越覺得代碼很難讀懂,煩。 寒假也過完了,在家里待了兩周的時間,感覺...
閱讀 1308·2023-04-26 01:03
閱讀 1943·2021-11-23 09:51
閱讀 3311·2021-11-22 15:24
閱讀 2672·2021-09-22 15:18
閱讀 1018·2019-08-30 15:55
閱讀 3487·2019-08-30 15:54
閱讀 2257·2019-08-30 15:53
閱讀 2398·2019-08-30 15:44