-
var?relative?=?{ ????">":{ ????????dir:"parentNode", ????????first:?true ????}, ????"?":{ ????????dir:?"parentNode" ????}, ????"+":{ ????????dir:"previousSibling", ????????first:?true ????}, ????"~":{ ????????dir:?"previousSibling" ????} }
function?addCombinator(elems){ ????var?elem; ????while((elem?=?elems['parentNode'])){ ????????if(elem.nodeType===1){ ????????????return?elem; ????????} ????} }
查看全部 -
CSS選擇器的位置關(guān)系
1、祖宗和后代??//?用空格 2、父親和兒子??//??用?>? 3、臨近兄弟??????//??用?+ 4、普通兄弟??????//??用?~
查看全部 -
Sizzle超級匹配器的流程:
div?>?p?+?div.aaron?input[type="checkbox"]
1、從右邊剝離出原生API能使用的接口屬性:id、class、tagname。所以找到了input,因為只可以用tag是查詢,但是此時結(jié)果是個合集,引入seed的概念,稱之為種子合集。
context.getElementsByTagName(?input?)
2、重組選擇器,踢掉input,得到新的tokens詞法元素哈希表
div?>?p?+?div.aaron?[type="checkbox"]
3、通過matcherFromTokens函數(shù),然后根據(jù)關(guān)系選擇器 【">","空","~","+"】拆分分組,因為DOM中的節(jié)點都是存在關(guān)系的,所以引入Expr.relative -> first:true 兩個關(guān)系的“緊密”程度,用于組合最佳的篩選。
4、依次按照如下順序解析并且編譯閉包函數(shù),編譯規(guī)則:div > p + div.aaron [type="checkbox"]。編譯成4組閉包函數(shù),然后在前后在合并組合成一組
div?> p?+ div.aaron? input[type="checkbox"]
????A. 抽出div元素,對應(yīng)的是TAG類型
????B.?通過Expr.filter找到對應(yīng)匹配的處理器,返回一個閉包處理器
????C.?將返回的curry方法放入到matchers匹配器組中,繼續(xù)分解
????D.?抽出子元素選擇器 '>' ,對應(yīng)的類型?type:?">"?
????E.?通過Expr.relative找到elementMatcher方法,分組合并多個詞素的的編譯函數(shù)
????F:返回的這個匹配器還是不夠的,因為沒有規(guī)范搜索范圍的優(yōu)先級,所以這時候還要引入addCombinator方法
????G:根據(jù)Expr.relative ->?first:true?兩個關(guān)系的“緊密”程度,找到對應(yīng)的第一個親密節(jié)點
查看全部 -
elementMatchers:就是通過分解詞法器生成的閉包函數(shù)了,也就是“終極匹配器
superMatcher遍歷seed
while?(?(matcher?=?elementMatchers[j++])?)?{ ????if?(?matcher(?elem,?context,?xml?)?)?{ ????????results.push(?elem?); ????????break; ????} }
為什么是while?
1、前面就提到了,tokenize選擇器是可以用過 “,”逗號分組 group,所以就會有個合集的概念了,matcher就得到了每一個終極匹配器。
2、通過代碼很能看出來matcher方法運行的結(jié)果都是bool值,對里面的元素逐個使用預先生成的matcher方法做匹配,如果結(jié)果為true的則直接將元素堆入返回結(jié)果集。
查看全部 -
superMatcher函數(shù)
1、這個方法并不是一個直接定義的方法,通過matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出來的一個curry化的函數(shù),但是最后執(zhí)行起重要作用的是它。
2、superMatcher這個方法并不是一個直接定義的方法,通過matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出來的一個curry化的函數(shù),但是最后執(zhí)行起重要作用的是它
3、superMatcher方法會根據(jù)參數(shù)seed 、expandContext和context確定一個起始的查詢范圍:有可能是直接從seed中查詢過濾,也有可能在context或者context的父節(jié)點范圍內(nèi)。如果不是從seed開始,那只能把整個DOM樹節(jié)點取出來過濾了,把整個DOM樹節(jié)點取出來過濾了,它會先執(zhí)行Expr.find["TAG"]( "*", outermost )這句代碼等到一個elems集合(數(shù)組合集)
elems?=?seed?||?byElement?&&?Expr.find["TAG"](?"*",?outermost?),
查看全部 -
Sizzle編譯原理(下)
1、matcherFromTokens,它充當了selector“分詞”與Expr中定義的匹配方法的串聯(lián)與紐帶的作用,可以說選擇符的各種排列組合都是能適應(yīng)的了。Sizzle巧妙的就是它沒有直接將拿到的“分詞”結(jié)果與Expr中的方法逐個匹配逐個執(zhí)行,而是先根據(jù)規(guī)則組合出一個大的匹配方法,最后一步執(zhí)行。
2、matcherFromTokens的分解是有規(guī)律的,語義節(jié)點+關(guān)系選擇器的組合,Expr.relative 匹配關(guān)系選擇器類型,當遇到關(guān)系選擇器時elementMatcher函數(shù)將matchers數(shù)組中的函數(shù)生成一個函數(shù)。
3、如果遇到關(guān)系選擇符就會合并分組了,通過elementMatcher生成一個終極匹配器
4、matcher為當前詞素前的“終極匹配器”,combinator為位置詞素,根據(jù)關(guān)系選擇器檢查,判斷elem的兄弟或者父親節(jié)點是否依次符合規(guī)則。
5、編譯的過程是一個遞歸深搜的過程。
整個流程總結(jié)就干了那么幾件事:
1、在Expr.filter找出每一個選擇器類型對應(yīng)的處理方法
2、從右邊往左,向父級匹配的時候,注意詞素關(guān)系,引入relative記錄這個映射的關(guān)系
3、把對應(yīng)的處理函數(shù)壓入matchers數(shù)組
整個編譯過程,其實粗看就是把函數(shù)一層一層包裝下去,之后通過匹配器傳入對應(yīng)的種子合集seed一層一層的解開
查看全部 -
每條選擇器規(guī)則最小的幾個單元可以劃分為:ATTR | CHILD | CLASS | ID | PSEUDO | TAG
在Sizzle里邊有一些工廠方法用來生成對應(yīng)的這些元匹配器,它就是Expr.filter。
Expr.filter?=?{ ????ATTR???:?function?(name,?operator,?check)?{ ????CHILD??:?function?(type,?what,?argument,?first,?last)?{ ????CLASS??:?function?(className)?{ ????ID?????:?function?(id)?{ ????PSEUDO?:?function?(pseudo,?argument)?{ ????TAG????:?function?(nodeNameSelector)?{ }
查看全部 -
兩層過濾查找:向上迭代查找最近的父級元素
function?addCombinator(elems)?{?? ????var?elem;?? ????while?((elem?=?elems['parentNode']))?{???? ????????if?(elem.nodeType?===?1)?{????? ????????????return?elem???? ????????}?? ????} };
查看全部 -
first的意思就是一個快速條件,因為“>”選擇器是一個很明確的父子關(guān)系所以通過標記first只需要查找一層即可。
function?addCombinator(elems)?{ ????????var?elem; ????????//?向上迭代查找 ????????while?((elem?=?elems['parentNode']))?{ ???????????????if?(elem.nodeType?===?1)?{ ???????????????????????return?elem ???????????????} ????????} };
查看全部 -
過濾效率問題:
按照解析原理,詞法分析,過濾器原理處理之后得到的種子合集通過一次用循環(huán)遞歸去匹配查找,這樣的效率是很慢的。
解決方式:
sizzle從給1.8開始就引入了編譯的概念,sizzle引入這個編譯函數(shù)主要的作用是為分詞的篩選,提高逐個匹配的效率,實現(xiàn)閉包緩存。
原理:
閉包是js的特性,我們經(jīng)常會用來作為私有變量的保存處理,那么sizzle就很好的利用了這一特性,把選擇器中每一個選擇原子都變成了函數(shù)的處理方法,然后通過閉包保存著。再緩存在內(nèi)存中去,這樣有重復使用的時候就會首先調(diào)用緩存。
查看全部 -
Sizzle過濾器原理(下)
針對選擇器的層級關(guān)系:
1、首先“>”與“空”是祖輩關(guān)系,這樣可以理解是線型的,那么我們只要遞歸檢測每次元素的 parentNode 屬性返回指定節(jié)點的父節(jié)點。2、同理“+”與“~”也是類似的兄弟關(guān)系,無非就是擴展的范圍不同,所以針對層級的關(guān)系問題。
jQuery引入了詞素關(guān)系:
relative:?{ ??">":?{ ????dir:?"parentNode", ????first:?true ??}, ??"?":?{ ????dir:?"parentNode" ??}, ??"+":?{ ????dir:?"previousSibling", ????first:?true ??}, ??"~":?{ ????dir:?"previousSibling" ??} }
查看全部 -
過濾處理我們需要考慮的問題:
1 怎么有效的匹配這些選擇器的最小判斷單元,也就是通過詞法分割出后的結(jié)果
2 如何處理層級選擇器的判斷問題過濾是通過一層一層往上回溯不斷的循環(huán)去查找,這樣雖然結(jié)果可以拿到,但是效率是非常低的。所以sizzle從1.8后采用了空間換時間的方式,通過把各種過濾器編譯成閉包的函數(shù),所以這個過程也可說是"編譯函數(shù)"。
其實我們看過濾器的就是一個具體的判斷方法,通過傳遞一個上下文元素,來判斷是否存在,得到這一個布爾值,這樣有效了緩存了重復的處理,來節(jié)約判斷的過程。
查看全部 -
屬性選擇器分支的單元結(jié)構(gòu):
"[input[name=ttt]]" matches?=?[ ???0:?"type" ???1:?"=" ???2:?"ttt" ] type:?"ATTR"? value:?[name=ttt]"
種子合集:通過三個基本查詢,生成種子合集
1、我們從右到左邊開始匹配最終合集單元,從左邊開始很明顯是屬性選擇器,但是“input[name=ttt]”原生的API是不認識的。這種標簽用Expr.find能匹配到了。find:ID、Tag、Class
2、這里引入了seed -?種子合集(搜索器搜到符合條件的標簽),放入到這個初始集合seed中。這種我們找到了最終的一個合集,那么我們需要的就是根據(jù)剩余的條件篩選出真正的選擇器就OK了。找到一個集合后就暫停,不再往下匹配了,因為如果再用這樣的方式往下匹配效率就慢了。
查看全部 -
元素的匹配器:
Expr.filter :TAG, ID, CLASS, ATTR, CHILD, PSEUDO
屬性選擇器有點復雜,通過第一次正則只能匹配器出整體,所以需要第二次分解,引入了Expr.preFilter,Expr.preFilter保留了3個兼容處理分別是ATTR,CHILD,PSEUDO復雜的選擇器。
查看全部 -
最終匹配結(jié)果的解構(gòu):
groups:?[ ??tokens:?{ ????matches:??,?type?:??,?value?:?? ??}, ??tokens:?{ ????matches:??,?type?:??,?value?:?? ??} ]
查看全部 -
什么是詞法分析器(tokenize):
詞法分析器又稱掃描器,詞法分析是指將我們編寫的文本代碼流解析為一個一個的記號,分析得到的記號以供后續(xù)語法分析使用
詞法分析涉及了3大塊
1、分組逗號
2、層級關(guān)系
3、每種元素處理
sizzle對于分組過濾處理都用正則,其中都有一個特點,就是都是元字符^開頭,限制匹配的初始,所以tokenize也是從左邊開始一層一層的剝離。其中會用到的正則有:
//分組 var?rcomma?=?/^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/; //關(guān)系符 var?rcombinators?=?/^[\x20\t\r\n\f]*([>+~]|[\x20\t\r\n\f])[\x20\t\r\n\f]*/; //空白 var?whitespace?=?"[\\x20\\t\\r\\n\\f]";?//?字符串要多加個斜線
查看全部 -
\x20 表示空格
\t 表示水平制表符。將當前位置移到下一個tab位置。
\r 表示回車。將當前位置移到本行的開頭。
\n 表示回車換行。將當前位置移到下一行的開頭。
\f 表示換頁。將當前位置移到下一頁的開頭。
查看全部 -
Sizzle解析原理:
1、瀏覽器支持高級API時,直接調(diào)用querySelectorAll
2、瀏覽器不支持高級API時,降級通過sizzle處理,那么內(nèi)部會有一個規(guī)則把選擇器分組groups,然后通過從右邊往左邊查找,加入編譯函數(shù)的方式節(jié)約重復查找的性能問題
一個節(jié)點跟另一個節(jié)點有以下幾種關(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é)點
查看全部 -
選擇器結(jié)尾加上「*」就大大降低了這種優(yōu)勢,這也就是很多優(yōu)化原則提到的盡量避免在選擇器末尾添加通配符的原因
查看全部 -
Sizzle設(shè)計思路:
我們知道CSS的匹配規(guī)則是從右邊向左篩選,jQuery在Sizzle中延續(xù)了這樣的算法,先搜尋頁面中所有的a標簽,在之后的操縱中再往后判定它的父節(jié)點(包括父節(jié)點以上)是否為div,一層一層往上過濾,最后返回該操縱序列。
瀏覽器渲染原理:
1、瀏覽器從下載文檔到顯示頁面的過程是個復雜的過程,這里包含了重繪和重排。各家瀏覽器引擎的工作原理略有差別,但也有一定規(guī)則。
2、簡單講,通常在文檔初次加載時,瀏覽器引擎會解析HTML文檔來構(gòu)建DOM樹,之后根據(jù)DOM元素的幾何屬性構(gòu)建一棵用于渲染的樹。渲染樹的每個節(jié)點都有大小和邊距等屬性,類似于盒子模型(由于隱藏元素不需要顯示,渲染樹中并不包含DOM樹中隱藏的元素)。
3、當渲染樹構(gòu)建完成后,瀏覽器就可以將元素放置到正確的位置了,再根據(jù)渲染樹節(jié)點的樣式屬性繪制出頁面。由于瀏覽器的流布局,對渲染樹的計算通常只需要遍歷一次就可以完成,所以我們知道瀏覽器最終會將HTML文檔(或者說頁面)解析成一棵DOM樹
瀏覽器提供的查找接口,基本靠譜的就只有三個:
Expr.find?=?{ ??????'ID'????:?context.getElementById, ??????'CLASS'?:?context.getElementsByClassName, ??????'TAG'???:?context.getElementsByTagName }
查看全部 -
Sizzle選擇器
不能不說jQuery的反模式非職責單一深受開發(fā)者喜歡,一個接口承載的職責越多內(nèi)部處理就越復雜了
jQuery查詢的對象是dom元素,查詢到目標元素后,如何存儲?
1、查詢的到結(jié)果儲存到j(luò)Query對象內(nèi)部,由于查詢的dom可能是單一元素,也可能是合集
2、jQuery內(nèi)部應(yīng)該要定義一個合集數(shù)組,用于存在選擇后的dom元素
3、當然啦,根據(jù)API,jQuery構(gòu)建的不僅僅只是DOM元素,還有HTML字符串、Object、[] 等等
源碼縮進后的結(jié)構(gòu):
1、處理""、null、undefined、false、返回this、增加程序的健壯性
2、處理字符串
3、處理DOMElement,返回修改過后的實例對象this
4、處理$(function(){})
查看全部
舉報