亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

淺談Sizzle的“編譯原理”

2019-11-20 12:41:34
字體:
來源:轉載
供稿:網友

Sizzle,是jQuery作者John Resig寫的DOM選擇器引擎,速度號稱業界第一。作為一個獨立全新的選擇器引擎,出現在jQuery 1.3版本之后,并被John Resig作為一個開源的項目。Sizzle是獨立的一部分,不依賴任何庫,如果你不想用jQuery,可以只用Sizzle,也可以用于其他框架如:Mool, Dojo,YUI等。

前幾天在準備一個關于jQuery的分享PPT,問同事關于jQuery除了使用方法之外還有沒有其他特別想了解一下的,有人提到了想了解下它的選擇器是怎么實現的,也有人提到jQuery的查詢速度跟其他框架比怎么樣。關于速度,sizzle的官方網站上可以下載測試的例子,速度確實很有優勢。但是它為什么會有這樣高效的運行速度,就跟這里想探討的實現原理有關系了。

在了解Sizzle之前必須要先了解它的選擇器是怎么回事,這里有一個簡單的例子,熟悉jQuery的同學也一定很熟悉這樣的選擇器格式:

復制代碼 代碼如下:

tag #id .class , a:first

它基本上是從左到右層層深入過濾去查找匹配的dom元素,這個語句還不算復雜。假設我們自己來實現這一條查詢語句的話,也不難。但是,查詢語句只有基本的規則,沒有固定的選擇符個數和順序,我們自己寫代碼怎樣能適應這種隨意的排列組合?Sizzle就能做到各種情況的正常解析、執行。

Sizzle的源碼確實錯綜復雜不容易理清楚它的思路。先拋開外面層層的包裹,直接看看我個人認為整個實現里很核心的三個方法:

第一個核心方法。源碼第1052行有一個tokenize函數:

復制代碼 代碼如下:

function tokenize(selector, parseOnly ) { }

第二個參數parseOnly為false的意思是只做token序列化操作,不返回結果,這個情況下序列化的結果會被緩存起來備用。Selector就是查詢語句了。

經過這個函數處理后,比如selector="#idtag.class , a:first"傳進去,可以得到一個格式類似于下面的結果:

[[{matches:" id ",type:"ID"},{matches:" tag ",type:"TAG"},{matches:" class ",type:"CLASS"},...],[    {matches:" a",type:"TAG"},    ...],[…],…]


看到tokenize這個函數的命名和它的作用,讓我很容易就聯想起“編譯原理”這個詞了。這里就有點像是詞法分析了,不過這個詞法分析比程序編譯時做的詞法分析簡單。

tokenize方法會根據selector里面的逗號,空格和關系選擇符的正則表達式做“分詞”,得到一個二維數組(請允許我冒用這個不是很準確的稱呼),其中第一維數組是根據逗號分隔出來的,在源代碼里面被稱作groups。

我們再看源代碼第405行開始有一個Expr = Sizzle.selectors = {}的定義,其中到567行的時候有一個filter的定義,這里我們能找到基本的過濾類型:"ID"、"TAG"、"CLASS"、"ATTR"、"CHILD"、"PSEUDO",tokenize最終分類出來的type也就是這幾種。

“分詞”完成之后,依舊看在405行定義的Expr= Sizzle.selectors = {}。這里面能找到我們熟悉的所有選擇符,每個選擇符對應一個方法定義。到這里應該想到Sizzle其實是不是就是通過對selector做“分詞”,打散之后再分別從Expr里面去找對應的方法來執行具體的查詢或者過濾的操作?

答案基本是肯定的。但是Sizzle有更具體和巧妙的做法。再來看我認為很核心的第二個方法:

源代碼1293行有一個matcherFromTokens函數:

復制代碼 代碼如下:

function matcherFromTokens(tokens) { }

傳入的參數正是從tokenize方法得到的。Matcher可以理解成是“匹配程序”的意思,光從字面上看這個函數起的作用就是通過tokens生成匹配程序。事實上確實如此。限于篇幅,這篇文章暫且只分享我理解到的一些Sizzle的實現原理,不貼源碼。后面有時間我或者再整理一篇更詳盡的源碼分析的文章。

matcherFromTokens方法證實了前面的設想,它充當了selector“分詞”與Expr中定義的匹配方法的串聯與紐帶的作用,可以說選擇符的各種排列組合都是能適應的了。Sizzle巧妙的就是它沒有直接將拿到的“分詞”結果與Expr中的方法逐個匹配逐個執行,而是先根據規則組合出一個大的匹配方法,最后一步執行。但是組合之后怎么執行的,還得再看關鍵的第三個方法:

源代碼1350行有一個superMatcher方法:

復制代碼 代碼如下:

superMatcher = function( seed, context, xml, results, expandContext ) { }

這個方法并不是一個直接定義的方法,而是通過1345行的matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出來的,但是最后執行起重要作用的是它。

superMatcher方法會根據參數seed、expandContext和context確定一個起始的查詢范圍,有可能是直接從seed中查詢過濾,也有可能在context或者context的父節點范圍內。如果不是從seed開始,那么它會先執行Expr.find["TAG"]( "*", expandContext && context.parentNode || context )這句代碼等到一個elems集合(數組)。然后對elems做一個遍歷,對里面的元素逐個使用預先生成的matcher方法做匹配,如果結果為true的則直接將元素堆入返回結果集里面。

好吧,看到這里matcher方法原來運行的結果都是bool值,我們再返回405行看一下Expr里面filter包含的方法,都是返回bool值的。包括PSEUDO(偽類)對應的更多的偽類方法都一樣。似乎有點顛覆我最初的設想,它原來不是一層一層往下查,卻有點倒回去向上做匹配、過濾的意思。Expr里面只有find和preFilter返回的是集合。

盡管到這里暫時還帶著一點疑問,就是最后它為什么用的是逐個匹配、過濾的方法來得到結果集,但是我想Sizzle最基本的“編譯原理”應該已經解釋清楚了。

但是疑問不能留著,我們繼續。其實這篇文章本身已經有點倒過來寫的味道了。有興趣看源碼的同學不會一開始就看到這三個關鍵的方法。實際上Sizzle在進入這三個方法之前,還做了一系列的其他工作。

Sizzle的真正入口可以說是在源碼的220行:

復制代碼 代碼如下:

function Sizzle( selector, context, results, seed ){ }

這個方法前面一段比較容易懂,如果能匹配到selector是單一的ID選擇符(#id),則先根據id就直接用context.getElementById( m )方法把元素找出來。如果能匹配到selector是單一的TAG選擇符,也先直接用context.getElementsByTagName( selector )方法把相關的元素找出來。如果當前瀏覽器只是原生的getElementsByClassName,并且匹配到selector是單一的CLASS選擇符,也會也用context.getElementsByClassName( m )方法把相關的元素找出來。這個三個方法,都是瀏覽器支持的原生方法,執行效率肯定是最高的。

如果最基本的方法都用不上的話,才會進入到select方法。源碼1480行有它的定義:

復制代碼 代碼如下:

function select( selector, context, results, seed, xml ) { }

在select方法里面,首先會對selector做我們前面提到的“分詞”操作。但是這個操作之后并沒有直接開始組裝匹配方法,而是先做了一些find的操作。這里的find操作就可以對應到Expr里面的find,它執行的是查詢操作,返回的是結果集。

可以這樣理解,select利用“分詞”得到的選擇符根據它的type先將可以用find方法查找的結果集查出來。做find操作的時候,是按照選擇符的順序從左到右縮小結果集范圍的。如果一個遍歷下來,selector中的所有選擇符都可以執行find操作,則直接將結果返回。否則,就進入前面介紹的“編譯”執行過濾的流程了。

到這里,也可以順過來,基本上理清楚Sizzle的工作流程了。前面留下的疑問到此時其實也不算疑問了,因為執行反向匹配過濾的時候,它的查找范圍已經是經過層層過濾的最小集合了。而反向匹配過濾的方法對于它所對應的那些選擇符,比如偽類之類的,其實也已經是一個高效的選擇。

再來簡單總結為什么Sizzle很高效。

首先,從處理流程上,它總是先使用最高效的原生方法來做處理。前面一直在介紹的還只是Sizzle自身的選擇器實現方法,真正Sizzle執行的時候,它還會先判斷當前瀏覽器是否支持querySelectorAll原生方法(源代碼1545行)。如果支持的話,則優先選用此方法,瀏覽器原生支持的方法,效率肯定比Sizzle自己js寫的方法要高,優先使用也能保證Sizzle更高的工作效率。(關于querySelectorAll可以上網查閱更多資料)。在不支持querySelectorAll方法的情況下,Sizzle也是優先判斷是不是可以直接使用getElementById、getElementsByTag、getElementsByClassName等方法解決問題。

其次,相對復雜的情況,Sizzle總是選擇先盡可能利用原生方法來查詢選擇來縮小待選范圍,然后才會利用前面介紹的“編譯原理”來對待選范圍的元素逐個匹配篩選。進入到“編譯”這個環節的工作流程有些復雜,效率相比前面的方法肯定會稍低一些,但Sizzle在努力盡量少用這些方法,同時也努力讓給這些方法處理的結果集盡量小和簡單,以便獲得更高的效率。

再次,即便進入到這個“編譯”的流程,Sizzle還做了我們前面為了優先解釋清楚流程而暫時忽略、沒有介紹的緩存機制。源代碼1535行是我們所謂的“編譯”入口,也就是它會調用第三個核心方法superMatcher。跟蹤進去看1466行,compile方法將根據selector生成的匹配函數緩存起來了。還不止如此,再到1052行看tokenize方法,它其實也將根據selector做的分詞結果緩存起來了。也就是說,當我們執行過一次Sizzle (selector)方法以后,下次再直接調用Sizzle (selector)方法,它內部最耗性能的“編譯”過程不會再耗太多性能了,直接取之前緩存的方法就可以了。我在想所謂“編譯”的最大好處之一可能也就是便于緩存,所謂“編譯”在這里可能也就可以理解成是生成預處理的函數存儲起來備用。

到此我希望基本能夠解答之前關心選擇器實現原理和執行效率問題的同學的疑問了。另外本文分析結論源自于Sizzle最新版本源碼,文中提到的代碼行號以此版本源碼為準,可以從http://sizzlejs.com/下載。時間倉促,分析有不周到的地方拍磚請手下留情,還有疑問的同學歡迎線下繼續交流。

以上所述就是本文的全部內容了,希望大家能夠喜歡。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人在线免费视频| 国产亚洲欧美日韩精品| 另类少妇人与禽zozz0性伦| 欧美国产日韩一区二区在线观看| 欧美日韩国产一区在线| 国内自拍欧美激情| 国产精品电影一区| 国产精品电影在线观看| 欧美成人午夜激情在线| 国产精品美女呻吟| 国产精品18久久久久久首页狼| 日日狠狠久久偷偷四色综合免费| 国内揄拍国内精品少妇国语| 久久久精品欧美| 免费97视频在线精品国自产拍| 日韩精品久久久久久福利| 国产精国产精品| 亚洲欧美精品伊人久久| 久久99久久亚洲国产| 成人黄色免费网站在线观看| 欧美视频裸体精品| 亚洲欧洲在线看| 日韩av手机在线观看| 国产一区二区三区丝袜| 一本一本久久a久久精品牛牛影视| 国产区亚洲区欧美区| 中文字幕亚洲图片| 日韩av毛片网| 欧美大片免费观看在线观看网站推荐| 日韩av一区在线观看| 精品调教chinesegay| 91精品国产91久久久久久久久| 午夜精品视频网站| 欧美人与性动交a欧美精品| 国产精品 欧美在线| 欧美久久久精品| 欧美成人激情视频免费观看| 日韩欧美精品中文字幕| 亚洲午夜性刺激影院| 欧洲永久精品大片ww免费漫画| 欧美丝袜一区二区三区| 日产精品99久久久久久| 美日韩丰满少妇在线观看| 成人黄色中文字幕| 久久久91精品国产| 欧美在线观看网址综合| 国产精品激情自拍| 欧美日韩性视频在线| 麻豆乱码国产一区二区三区| 伊人久久久久久久久久久| 亚洲精品国产免费| 欧美性在线观看| 欧美丰满少妇xxxxx| 久久97久久97精品免视看| 亚洲男人的天堂在线播放| 日韩三级影视基地| 国产欧美va欧美va香蕉在| 亚洲精品久久久久中文字幕欢迎你| 视频一区视频二区国产精品| 亚洲资源在线看| 亚洲成人免费在线视频| 黄色精品在线看| 正在播放欧美视频| 久久精品免费播放| 日韩性xxxx爱| 亚洲最大的网站| 91精品国产高清久久久久久久久| 国产精品999| 日韩欧美综合在线视频| 欧美综合激情网| 色婷婷av一区二区三区久久| 国产福利精品视频| 精品五月天久久| 成人黄色大片在线免费观看| 色偷偷av亚洲男人的天堂| 亚洲欧美日韩天堂一区二区| 97视频色精品| 91精品免费视频| 久久精品视频播放| 国产日韩欧美自拍| 亚洲成人av资源网| 欧美性生活大片免费观看网址| 2019中文字幕在线| 狠狠躁夜夜躁人人躁婷婷91| 91色视频在线观看| 精品动漫一区二区| 亚洲日本中文字幕免费在线不卡| 久久久久久91香蕉国产| 日韩欧美亚洲综合| 欧美激情亚洲精品| 久久久久久久一区二区| 成人性生交大片免费观看嘿嘿视频| 国内精品国产三级国产在线专| 国产一区二区三区三区在线观看| 成人在线免费观看视视频| 91精品在线观看视频| 在线观看久久av| 最近2019中文字幕mv免费看| 中文字幕久热精品视频在线| 色噜噜狠狠狠综合曰曰曰88av| 久久久国产成人精品| 在线观看不卡av| 一本大道久久加勒比香蕉| 97精品一区二区视频在线观看| 欧美性猛交xxxx免费看漫画| 日韩人体视频一二区| 国产精品久久久久久久7电影| 亚洲精选中文字幕| 亚洲精品美女久久久久| 久久久久亚洲精品成人网小说| 国产精品欧美久久久| 色播久久人人爽人人爽人人片视av| 日本精品视频在线播放| 亚洲国产精品热久久| 久久久久久久久久亚洲| 日韩中文字幕在线视频| 欧美日韩国产丝袜美女| 日韩av在线网址| 色噜噜久久综合伊人一本| 欧美日韩国产一区二区三区| 日韩欧美国产高清91| 538国产精品一区二区免费视频| 5252色成人免费视频| 国产美女精彩久久| 91理论片午午论夜理片久久| 亚洲天堂视频在线观看| 日韩av一卡二卡| 成人女保姆的销魂服务| 成人免费网站在线| 国产精品高清免费在线观看| 永久免费精品影视网站| 久久久国产视频| 秋霞成人午夜鲁丝一区二区三区| 日韩一区二区三区在线播放| 中文字幕视频一区二区在线有码| 久久激情视频久久| 亚洲高清福利视频| 欧美激情精品久久久久久免费印度| 欧美激情一区二区三区久久久| 日本精品va在线观看| 2019中文在线观看| 亚洲欧美日韩一区二区三区在线| 97人人模人人爽人人喊中文字| 久久久久久av| 91精品视频免费观看| 国产精品永久免费视频| 日本视频久久久| 日韩暖暖在线视频| 亚洲天堂第二页| 国产亚洲人成a一在线v站| 国产一区二区视频在线观看| 91午夜理伦私人影院| 中文精品99久久国产香蕉| 亚洲精品一区在线观看香蕉| 亚洲美女中文字幕| 久热爱精品视频线路一| 欧美大片在线看免费观看| 一区二区在线免费视频| 欧美在线视频观看| 色综合亚洲精品激情狠狠| 久热爱精品视频线路一| 久久福利网址导航| 亚洲精品一区在线观看香蕉| 日韩中文字幕免费视频|