最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Sizzle的“編譯原理”

 更新時(shí)間:2015年04月14日 10:05:49   投稿:hebedich  
正在學(xué)習(xí)Sizzle源碼或有一定前端基礎(chǔ)的同學(xué)們,可以一邊看源碼一邊看這些文章進(jìn)行驗(yàn)證,所以雖然我會(huì)分析源碼中的正則表達(dá)式,有大量的注釋?zhuān)粫?huì)講正則表達(dá)式的基本用法!

Sizzle,是jQuery作者John Resig寫(xiě)的DOM選擇器引擎,速度號(hào)稱(chēng)業(yè)界第一。作為一個(gè)獨(dú)立全新的選擇器引擎,出現(xiàn)在jQuery 1.3版本之后,并被John Resig作為一個(gè)開(kāi)源的項(xiàng)目。Sizzle是獨(dú)立的一部分,不依賴(lài)任何庫(kù),如果你不想用jQuery,可以只用Sizzle,也可以用于其他框架如:Mool, Dojo,YUI等。

前幾天在準(zhǔn)備一個(gè)關(guān)于jQuery的分享PPT,問(wèn)同事關(guān)于jQuery除了使用方法之外還有沒(méi)有其他特別想了解一下的,有人提到了想了解下它的選擇器是怎么實(shí)現(xiàn)的,也有人提到j(luò)Query的查詢(xún)速度跟其他框架比怎么樣。關(guān)于速度,sizzle的官方網(wǎng)站上可以下載測(cè)試的例子,速度確實(shí)很有優(yōu)勢(shì)。但是它為什么會(huì)有這樣高效的運(yùn)行速度,就跟這里想探討的實(shí)現(xiàn)原理有關(guān)系了。

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

復(fù)制代碼 代碼如下:

tag #id .class , a:first

它基本上是從左到右層層深入過(guò)濾去查找匹配的dom元素,這個(gè)語(yǔ)句還不算復(fù)雜。假設(shè)我們自己來(lái)實(shí)現(xiàn)這一條查詢(xún)語(yǔ)句的話(huà),也不難。但是,查詢(xún)語(yǔ)句只有基本的規(guī)則,沒(méi)有固定的選擇符個(gè)數(shù)和順序,我們自己寫(xiě)代碼怎樣能適應(yīng)這種隨意的排列組合?Sizzle就能做到各種情況的正常解析、執(zhí)行。

Sizzle的源碼確實(shí)錯(cuò)綜復(fù)雜不容易理清楚它的思路。先拋開(kāi)外面層層的包裹,直接看看我個(gè)人認(rèn)為整個(gè)實(shí)現(xiàn)里很核心的三個(gè)方法:

第一個(gè)核心方法。源碼第1052行有一個(gè)tokenize函數(shù):

復(fù)制代碼 代碼如下:

function tokenize(selector, parseOnly ) { }

第二個(gè)參數(shù)parseOnly為false的意思是只做token序列化操作,不返回結(jié)果,這個(gè)情況下序列化的結(jié)果會(huì)被緩存起來(lái)備用。Selector就是查詢(xún)語(yǔ)句了。

經(jīng)過(guò)這個(gè)函數(shù)處理后,比如selector="#idtag.class , a:first"傳進(jìn)去,可以得到一個(gè)格式類(lèi)似于下面的結(jié)果:

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


看到tokenize這個(gè)函數(shù)的命名和它的作用,讓我很容易就聯(lián)想起“編譯原理”這個(gè)詞了。這里就有點(diǎn)像是詞法分析了,不過(guò)這個(gè)詞法分析比程序編譯時(shí)做的詞法分析簡(jiǎn)單。

tokenize方法會(huì)根據(jù)selector里面的逗號(hào),空格和關(guān)系選擇符的正則表達(dá)式做“分詞”,得到一個(gè)二維數(shù)組(請(qǐng)?jiān)试S我冒用這個(gè)不是很準(zhǔn)確的稱(chēng)呼),其中第一維數(shù)組是根據(jù)逗號(hào)分隔出來(lái)的,在源代碼里面被稱(chēng)作groups。

我們?cè)倏丛创a第405行開(kāi)始有一個(gè)Expr = Sizzle.selectors = {}的定義,其中到567行的時(shí)候有一個(gè)filter的定義,這里我們能找到基本的過(guò)濾類(lèi)型:"ID"、"TAG"、"CLASS"、"ATTR"、"CHILD"、"PSEUDO",tokenize最終分類(lèi)出來(lái)的type也就是這幾種。

“分詞”完成之后,依舊看在405行定義的Expr= Sizzle.selectors = {}。這里面能找到我們熟悉的所有選擇符,每個(gè)選擇符對(duì)應(yīng)一個(gè)方法定義。到這里應(yīng)該想到Sizzle其實(shí)是不是就是通過(guò)對(duì)selector做“分詞”,打散之后再分別從Expr里面去找對(duì)應(yīng)的方法來(lái)執(zhí)行具體的查詢(xún)或者過(guò)濾的操作?

答案基本是肯定的。但是Sizzle有更具體和巧妙的做法。再來(lái)看我認(rèn)為很核心的第二個(gè)方法:

源代碼1293行有一個(gè)matcherFromTokens函數(shù):

復(fù)制代碼 代碼如下:

function matcherFromTokens(tokens) { }

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

matcherFromTokens方法證實(shí)了前面的設(shè)想,它充當(dāng)了selector“分詞”與Expr中定義的匹配方法的串聯(lián)與紐帶的作用,可以說(shuō)選擇符的各種排列組合都是能適應(yīng)的了。Sizzle巧妙的就是它沒(méi)有直接將拿到的“分詞”結(jié)果與Expr中的方法逐個(gè)匹配逐個(gè)執(zhí)行,而是先根據(jù)規(guī)則組合出一個(gè)大的匹配方法,最后一步執(zhí)行。但是組合之后怎么執(zhí)行的,還得再看關(guān)鍵的第三個(gè)方法:

源代碼1350行有一個(gè)superMatcher方法:

復(fù)制代碼 代碼如下:

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

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

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

好吧,看到這里matcher方法原來(lái)運(yùn)行的結(jié)果都是bool值,我們?cè)俜祷?05行看一下Expr里面filter包含的方法,都是返回bool值的。包括PSEUDO(偽類(lèi))對(duì)應(yīng)的更多的偽類(lèi)方法都一樣。似乎有點(diǎn)顛覆我最初的設(shè)想,它原來(lái)不是一層一層往下查,卻有點(diǎn)倒回去向上做匹配、過(guò)濾的意思。Expr里面只有find和preFilter返回的是集合。

盡管到這里暫時(shí)還帶著一點(diǎn)疑問(wèn),就是最后它為什么用的是逐個(gè)匹配、過(guò)濾的方法來(lái)得到結(jié)果集,但是我想Sizzle最基本的“編譯原理”應(yīng)該已經(jīng)解釋清楚了。

但是疑問(wèn)不能留著,我們繼續(xù)。其實(shí)這篇文章本身已經(jīng)有點(diǎn)倒過(guò)來(lái)寫(xiě)的味道了。有興趣看源碼的同學(xué)不會(huì)一開(kāi)始就看到這三個(gè)關(guān)鍵的方法。實(shí)際上Sizzle在進(jìn)入這三個(gè)方法之前,還做了一系列的其他工作。

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

復(fù)制代碼 代碼如下:

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

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

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

復(fù)制代碼 代碼如下:

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

在select方法里面,首先會(huì)對(duì)selector做我們前面提到的“分詞”操作。但是這個(gè)操作之后并沒(méi)有直接開(kāi)始組裝匹配方法,而是先做了一些find的操作。這里的find操作就可以對(duì)應(yīng)到Expr里面的find,它執(zhí)行的是查詢(xún)操作,返回的是結(jié)果集。

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

到這里,也可以順過(guò)來(lái),基本上理清楚Sizzle的工作流程了。前面留下的疑問(wèn)到此時(shí)其實(shí)也不算疑問(wèn)了,因?yàn)閳?zhí)行反向匹配過(guò)濾的時(shí)候,它的查找范圍已經(jīng)是經(jīng)過(guò)層層過(guò)濾的最小集合了。而反向匹配過(guò)濾的方法對(duì)于它所對(duì)應(yīng)的那些選擇符,比如偽類(lèi)之類(lèi)的,其實(shí)也已經(jīng)是一個(gè)高效的選擇。

再來(lái)簡(jiǎn)單總結(jié)為什么Sizzle很高效。

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

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

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

到此我希望基本能夠解答之前關(guān)心選擇器實(shí)現(xiàn)原理和執(zhí)行效率問(wèn)題的同學(xué)的疑問(wèn)了。另外本文分析結(jié)論源自于Sizzle最新版本源碼,文中提到的代碼行號(hào)以此版本源碼為準(zhǔn),可以從http://sizzlejs.com/下載。時(shí)間倉(cāng)促,分析有不周到的地方拍磚請(qǐng)手下留情,還有疑問(wèn)的同學(xué)歡迎線下繼續(xù)交流。

以上所述就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。

相關(guān)文章

  • 聯(lián)合類(lèi)型Union?Types與交叉類(lèi)型Intersection?Types區(qū)別解析

    聯(lián)合類(lèi)型Union?Types與交叉類(lèi)型Intersection?Types區(qū)別解析

    這篇文章主要為大家介紹了聯(lián)合類(lèi)型Union?Types與交叉類(lèi)型Intersection?Types區(qū)別詳解
    2023-06-06
  • TypeScript學(xué)習(xí)輕松玩轉(zhuǎn)類(lèi)型操作

    TypeScript學(xué)習(xí)輕松玩轉(zhuǎn)類(lèi)型操作

    這篇文章主要為大家介紹了TypeScript學(xué)習(xí)輕松玩轉(zhuǎn)類(lèi)型操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • TS 中的類(lèi)型推斷與放寬實(shí)例詳解

    TS 中的類(lèi)型推斷與放寬實(shí)例詳解

    這篇文章主要為大家介紹了TS 中的類(lèi)型推斷與放寬實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • require.js使用方法的簡(jiǎn)單代碼講解筆記

    require.js使用方法的簡(jiǎn)單代碼講解筆記

    頁(yè)面需要加載多個(gè)js文件時(shí),加載時(shí)瀏覽器會(huì)停止網(wǎng)頁(yè)渲染,加載文件越多,網(wǎng)頁(yè)失去響應(yīng)的時(shí)間就會(huì)越長(zhǎng);由于js文件之間存在依賴(lài)關(guān)系,必須嚴(yán)格保證加載順序,當(dāng)依賴(lài)關(guān)系很復(fù)雜的時(shí)候,代碼的編寫(xiě)和維護(hù)都會(huì)變得困難。這種情況下require.js插件應(yīng)運(yùn)而生。
    2022-12-12
  • 移動(dòng)設(shè)備web開(kāi)發(fā)首選框架:zeptojs介紹

    移動(dòng)設(shè)備web開(kāi)發(fā)首選框架:zeptojs介紹

    這篇文章主要介紹了移動(dòng)設(shè)備web開(kāi)發(fā)首選框架:zeptojs介紹,他兼容jquery的API,所以學(xué)起來(lái)或用起來(lái)并不吃力,需要的朋友可以參考下
    2015-01-01
  • TypeScript?中?as?const使用介紹

    TypeScript?中?as?const使用介紹

    這篇文章主要為大家介紹了TypeScript?中?as?const使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解

    數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解

    這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • TypeScript快速上手—html中使用ts的兩種方法

    TypeScript快速上手—html中使用ts的兩種方法

    TypeScript使用命令行編譯器tsc或其他工具手動(dòng)執(zhí)行編譯,在html使用s時(shí)編譯為JavaScript,那么有沒(méi)有辦法簡(jiǎn)化過(guò)程,不編譯直接使用,本文介紹html中使用TypeScript的兩種方法
    2024-07-07
  • laytpl 精致巧妙的JavaScript模板引擎

    laytpl 精致巧妙的JavaScript模板引擎

    laytpl是一款顛覆性的JavaScript模板引擎,它用巧妙的實(shí)現(xiàn)方式,將自身的體積變得小巧玲瓏,不僅性能接近極致,并且還具備傳統(tǒng)前端引擎的幾乎所有功能
    2014-08-08
  • js庫(kù)Modernizr的介紹和使用

    js庫(kù)Modernizr的介紹和使用

    Modernizr是一個(gè)開(kāi)源的JS庫(kù),它使得那些基于訪客瀏覽器的不同(指對(duì)新標(biāo)準(zhǔn)支持性的差異)而開(kāi)發(fā)不同級(jí)別體驗(yàn)的設(shè)計(jì)師的工作變得更為簡(jiǎn)單
    2015-05-05

最新評(píng)論

庆城县| 浮梁县| 铜梁县| 雷山县| 铁岭县| 蒲城县| 翼城县| 兰考县| 岱山县| 隆子县| 洪泽县| 湛江市| 革吉县| 乌鲁木齐市| 邛崃市| 古浪县| 龙泉市| 鄱阳县| 丰城市| 松滋市| 鄢陵县| 霞浦县| 中牟县| 章丘市| 平和县| 赣榆县| 襄汾县| 板桥市| 西青区| 三原县| 永福县| 建阳市| 松桃| 延寿县| 衡阳市| 临泉县| 吕梁市| 托里县| 黄梅县| 烟台市| 阿合奇县|