jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理
聲明:本文為原創(chuàng)文章,如需轉(zhuǎn)載,請注明來源并保留原文鏈接Aaron,謝謝!
先來回答博友的提問:
如何解析
div > p + div.aaron input[type="checkbox"]順便在深入理解下解析的原理:
HTML結(jié)構(gòu)
<div id="text"><p><input type="text" /></p><div class="aaron"><input type="checkbox" name="readme" value="Submit" /><p>Sizzle</p></div> </div>選擇器語句
div > p + div.aaron input[type="checkbox"]組合后的意思大概就是:
1. 選擇父元素為 <div> 元素的所有子元素 <p> 元素
2. 選擇緊接在 <p> 元素之后的所有 <div> 并且class="aaron " 的所有元素
3. 之后選擇 div.aaron 元素內(nèi)部的所有 input并且?guī)в?type="checkbox" 的元素
就針對這個簡單的結(jié)構(gòu),我們實際中是不可能這么寫的,但是這里我用簡單的結(jié)構(gòu),描述出復雜的處理
我們用組合語句,jquery中,在高級瀏覽器上都是用過querySelectorAll處理的,所以我們討論的都是在低版本上的實現(xiàn),偽類選擇器,XML 要放到后最后,本文暫不涉及這方便的處理.
?
需要用到的幾個知識點:
1: CSS選擇器的位置關(guān)系
2: CSS的瀏覽器實現(xiàn)的基本接口
3: CSS選擇器從右到左掃描匹配
?
CSS選擇器的位置關(guān)系
文檔中的所有節(jié)點之間都存在這樣或者那樣的關(guān)系
其實不難發(fā)現(xiàn),一個節(jié)點跟另一個節(jié)點有以下幾種關(guān)系:
祖宗和后代
父親和兒子???
臨近兄弟
普通兄弟
在CSS選擇器里邊分別是用:空格;>;+;~
(其實還有一種關(guān)系:div.aaron,中間沒有空格表示了選取一個class為aaron的div節(jié)點)
<div id="grandfather"><div id="father"><div id="child1"></div><div id="child2"></div><div id="child3"></div></div> </div>- 爺爺grandfather與孫子child1屬于祖宗與后代關(guān)系(空格表達)
- 父親father與兒子child1屬于父子關(guān)系,也算是祖先與后代關(guān)系(>表達)
- 哥哥child1與弟弟child2屬于臨近兄弟關(guān)系(+表達)
- 哥哥child1與弟弟child2,弟弟child3都屬于普通兄弟關(guān)系(~表達)
?
在Sizzle里有一個對象是記錄跟選擇器相關(guān)的屬性以及操作:Expr。它有以下屬性:
relative = {">": { dir: "parentNode", first: true }," ": { dir: "parentNode" },"+": { dir: "previousSibling", first: true },"~": { dir: "previousSibling" } }所以在Expr.relative里邊定義了一個first屬性,用來標識兩個節(jié)點的“緊密”程度,例如父子關(guān)系和臨近兄弟關(guān)系就是緊密的。在創(chuàng)建位置匹配器時,會根據(jù)first屬性來匹配合適的節(jié)點。
?
CSS的瀏覽器實現(xiàn)的基本接口
除去querySelector,querySelectorAll
HTML文檔一共有這么四個API:
- getElementById,上下文只能是HTML文檔。
- getElementsByName,上下文只能是HTML文檔。
- getElementsByTagName,上下文可以是HTML文檔,XML文檔及元素節(jié)點。
- getElementsByClassName,上下文可以是HTML文檔及元素節(jié)點。IE8還沒有支持。
所以要兼容的話sizzle最終只會有三種完全靠譜的可用
Expr.find = {'ID' : context.getElementById,'CLASS' : context.getElementsByClassName,'TAG' : context.getElementsByTagName }?
CSS選擇器從右到左掃描匹配
接下我們就開始分析解析規(guī)則了
1. 選擇器語句
div > p + div.aaron input[type="checkbox"]2. 開始通過詞法分析器tokenize分解對應(yīng)的規(guī)則(這個上一章具體分析過了)
分解每一個小塊 type: "TAG" value: "div" matches ....type: ">" value: " > "type: "TAG" value: "p" matches ....type: "+" value: " + "type: "TAG" value: "div" matches ....type: "CLASS" value: ".aaron" matches ....type: " " value: " "type: "TAG" value: "input" matches ....type: "ATTR" value: "[type="checkbox"]" matches ....除去關(guān)系選擇器,其余的有語意的標簽都都對應(yīng)這分析出matches比如 最后一個屬性選擇器分支 "[type="checkbox"]"matches = [0: "type"1: "="2: "checkbox" ] type: "ATTR" value: "[type="checkbox"]"所以就分解出了9個部分了
那么如何匹配才是最有效的方式?
3. 從右往左匹配
最終還是通過瀏覽器提供的API實現(xiàn)的, 所以Expr.find就是最終的實現(xiàn)接口了
首先確定的肯定是從右邊往左邊匹配,但是右邊第一個是
"[type="checkbox"]"很明顯Expr.find 中不認識這種選擇器,所以只能在往前扒一個
趴到了
type: "TAG" value: "input"這種標簽Expr.find能匹配到了,所以直接調(diào)用
Expr.find["TAG"] = support.getElementsByTagName ?function(tag, context) {if (typeof context.getElementsByTagName !== strundefined) {return context.getElementsByTagName(tag);} } :但是getElementsByTagName方法返回的是一個合集
所以
這里引入了seed - 種子合集(搜索器搜到符合條件的標簽),放入到這個初始集合seed中
OK了 這里暫停了,不在往下匹配了,在用這樣的方式往下匹配效率就慢了
開始整理:
重組一下選擇器,剔掉已經(jīng)在用于處理的tag標簽,input
所以選擇器變成了:
selector: "div > p + div.aaron [type="checkbox"]"這里可以優(yōu)化下,如果直接剔除后,為空了,就證明滿足了匹配要求,直接返回結(jié)果了
到這一步為止
我們能夠使用的東東:
1 seed合集
2 通過tokenize分析解析規(guī)則組成match合集
本來是9個規(guī)則快,因為匹配input,所以要對應(yīng)的也要踢掉一個所以就是8個了3 選擇器語句,對應(yīng)的踢掉了input
"div > p + div.aaron [type="checkbox"]"此時send目標合集有2個最終元素了
那么如何用最簡單,最有效率的方式從2個條件中找到目標呢?
?
涉及的源碼:
//引擎的主要入口函數(shù) function select(selector, context, results, seed) {var i, tokens, token, type, find,//解析出詞法格式match = tokenize(selector);if (!seed) { //如果外界沒有指定初始集合seed了。// Try to minimize operations if there is only one group// 沒有多組的情況下// 如果只是單個選擇器的情況,也即是沒有逗號的情況:div, p,可以特殊優(yōu)化一下if (match.length === 1) {// Take a shortcut and set the context if the root selector is an IDtokens = match[0] = match[0].slice(0); //取出選擇器Token序列//如果第一個是selector是id我們可以設(shè)置context快速查找if (tokens.length > 2 && (token = tokens[0]).type === "ID" &&support.getById && context.nodeType === 9 && documentIsHTML &&Expr.relative[tokens[1].type]) {context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0];if (!context) {//如果context這個元素(selector第一個id選擇器)都不存在就不用查找了return results;}//去掉第一個id選擇器selector = selector.slice(tokens.shift().value.length);}// Fetch a seed set for right-to-left matching//其中: "needsContext"= new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )//即是表示如果沒有一些結(jié)構(gòu)偽類,這些是需要用另一種方式過濾,在之后文章再詳細剖析。//那么就從最后一條規(guī)則開始,先找出seed集合i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;//從右向左邊查詢while (i--) { //從后開始向前找!token = tokens[i]; //找到后邊的規(guī)則// Abort if we hit a combinator// 如果遇到了關(guān)系選擇器中止//// > + ~ 空// if (Expr.relative[(type = token.type)]) {break;}/*先看看有沒有搜索器find,搜索器就是瀏覽器一些原生的取DOM接口,簡單的表述就是以下對象了Expr.find = {'ID' : context.getElementById,'CLASS' : context.getElementsByClassName,'NAME' : context.getElementsByName,'TAG' : context.getElementsByTagName}*///如果是:first-child這類偽類就沒有對應(yīng)的搜索器了,此時會向前提取前一條規(guī)則tokenif ((find = Expr.find[type])) {// Search, expanding context for leading sibling combinators// 嘗試一下能否通過這個搜索器搜到符合條件的初始集合seedif ((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//把最后一條規(guī)則去除掉tokens.splice(i, 1);selector = seed.length && toSelector(tokens);//看看當前剩余的選擇器是否為空if (!selector) {//是的話,提前返回結(jié)果了。 push.apply(results, seed);return results;}//已經(jīng)找到了符合條件的seed集合,此時前邊還有其他規(guī)則,跳出去break;}}}}}// "div > p + div.aaron [type="checkbox"]"// Compile and execute a filtering function// Provide `match` to avoid retokenization if we modified the selector above// 交由compile來生成一個稱為終極匹配器// 通過這個匹配器過濾seed,把符合條件的結(jié)果放到results里邊//// //生成編譯函數(shù)// var superMatcher = compile( selector, match )//// //執(zhí)行// superMatcher(seed,context,!documentIsHTML,results,rsibling.test( selector ))// compile(selector, match)(seed,context, !documentIsHTML,results,rsibling.test(selector));return results;}?
這個過程在簡單總結(jié)一下:
selector:"div > p + div.aaron input[type="checkbox"]"解析規(guī)則: 1 按照從右到左 2 取出最后一個token 比如[type="checkbox"]{matches : Array[3]type : "ATTR"value : "[type="checkbox "]"} 3 過濾類型 如果type是 > + ~ 空 四種關(guān)系選擇器中的一種,則跳過,在繼續(xù)過濾 4 直到匹配到為 ID,CLASS,TAG 中一種 , 因為這樣才能通過瀏覽器的接口索取 5 此時seed種子合集中就有值了,這樣把刷選的條件給縮的很小了 6 如果匹配的seed的合集有多個就需要進一步的過濾了,修正選擇器 selector: "div > p + div.aaron [type="checkbox"]" 7 OK,跳到一下階段的編譯函數(shù)?
Sizzle不僅僅是簡簡單單的從右往左匹配的
Sizzle1.8開始引入編譯函數(shù)的概念,也是下一章的重點
轉(zhuǎn)載于:https://www.cnblogs.com/aaronjs/p/3310937.html
總結(jié)
以上是生活随笔為你收集整理的jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle中drop、delete和t
- 下一篇: Killzone's AI: dynam