JQuery Sizzle引擎原始碼分析

來源:互聯網
上載者:User

標籤:

    最近在拜讀艾倫在慕課網上寫的JQuery課程,感覺在國內對JQuery程式碼分析透徹的人沒幾個能比得過艾倫。有沒有吹牛?是不是我說大話了?

什麼是Sizzle引擎?

    我們經常使用JQuery的選取器查詢元素,查詢的選取器有簡單也有複雜:
    簡單點:“div”、“.navi”、“div.navi”。

    複雜點:"div input[type=‘checkbox‘]"、"div.navi + .section p"。

   

    Query實現查詢時也是優先使用DOM標準查詢函數,例如:

    document.getElementById()

    document.getElementsByTagName()

    document.getElementsByClassName()

    document.getElementsByName()

    進階瀏覽器還實現了:

    querySelector()

    querySelectorAll()

    由於瀏覽器版本差異導致的相容問題,上面的函數並不是所有瀏覽器都支援。但JQuery得解決這些問題,所以就引入了Sizzle引擎。
JQuery在篩選元素時優先使用瀏覽器內建的進階查詢函數,因為查詢效率高。其次才選擇使用Sizzle引擎篩選元素。

    Sizzle引擎的目的是根據傳入的selector選取器篩選出元素集合。執行過程經過詞法分析、編譯過程。通過詞法分析把一個selector字串分解成結構化的資料以便編譯過程使用。編譯過程充分利用了Javascript的閉包功能,產生一個函數鏈,在最終匹配時再去執行這個函數鏈。

    舉個例子,一個選取器selector的值為”Aaron input[name=ttt]”,通過詞法分析,得到一個結構化數組:

[    {        matches: ["div"],        type: "TAG",        value: "Aaron"    },    {        type: " ",        value: " "    },    {        matches: ["name", "=", "ttt"],        type: "ATTR",        value: "[name=ttt]"    }]

    selector中的input作為一個種子集合seed。意思是Sizzle根據input查詢出所有input元素,結果存放到seed集合,編譯過程都是在seed集合中查詢過濾。
    上面說的很粗糙,不便於理解,接下來我們就拿代碼來介紹。

通過程式碼分析原理

    申明:下面的代碼來源於Aaron在慕課網上的Jquery教程

 

    compile

/** * 編譯過程 */function compile(){    var seed = document.querySelectorAll("input"),        selector = "Aaron [name=ttt]",        elementMatchers = [],        match = [            {                matches: ["div"],                type: "TAG",                value: "Aaron"            },            {                type: " ",                value: " "            },            {                matches: ["name", "=", "ttt"],                type: "ATTR",                value: "[name=ttt]"            }        ];    elementMatchers.push(matcherFromTokens(match));    //超級匹配器    var cached = matcherFromGroupMatchers(elementMatchers);    var results = cached(seed);    results[0].checked = ‘checked‘;}

    JQuery的compile函數包含了所有的執行過程,由於本篇介紹的重點是編譯過程,所以詞法分析的過程未包含,這裡直接寫了match結果,實際JQuery會調用tokenize()函數擷取片語。
    函數中調用了兩個函數:matcherFromTokens()和matcherFromGroupMatchers()。
    matcherFromTokens():返回的是一個函數,函數結構如下:

    返回函數格式為function(elem, context, xml),並且這個函數返回一個bool值表示elem是否匹配有效。

    matcherFromGroupMatchers():函數代碼很簡單,遍曆seed集合,每個元素都調用elementMatcher函數。最終返回一個匹配成功的元素集合。
    由於matcherFromGroupMatchers()函數比較簡單,所以就先介紹它。

   

    matcherFromGroupMatchers

function matcherFromGroupMatchers(elmentMatchers){    return function(seed){        var results = [];        var matcher, elem;        for(var i = 0; i < seed.length; i++){            var elem = seed[i];            matcher = elmentMatchers[0];            if(matcher(elem)){                results.push(elem);            }        }        return results;    }}

    遍曆seed元素,每一個元素都調用matcher函數,返回true則添加到results數組中。

 

  matcherFromTokens
function matcherFromTokens(tokens){    var len = tokens.length,        matcher,        matchers = [];    for(var i = 0; i < len; i++){        if(tokens[i].type === " "){            matchers = [addCombinator(elementMatcher(matchers))];        }else{            matcher = filter[tokens[i].type].apply(null, tokens[i].matches);            matchers.push(matcher);        }    }    return elementMatcher(matchers);}

    整個編譯的核心也就在matcherFromTokens函數中,遍曆分詞tokens數組,分詞分兩大類,關係型和非關係型。關係型包括:“ ”、“>”、“+”、“~”。 剩下的都是非關係型分詞。

    每一個非關係型分詞都會對應一個matcher:

    第一個分詞類型為TAG,在filter中找到matcher。第二個分詞為關係分詞,調用addCombinator合并之前的matcher。第三個分詞類型為ATTR,在filter中找到matcher。最終matchers的值為:

    在return的時候又調用了elementMatcher()函數,返回的結果還是一個函數。上面介紹compile函數時看到過返回的函數結構。
    matcherFromTokens函數體中有用到addCombinator()和elementMatcher()函數以及filter對象。先看filter:

var filter = {    ATTR: function(name, operator,check){        return function(elem){            var attr = elem.getAttribute(name);            if(operator === "="){                if(attr === check){                    return true;                }            }            return false;        }    },    TAG: function(nodeNameSelector){        return function(elem){            return elem.nodeName && elem.nodeName.toLowerCase() === nodeNameSelector;        }    }}

    一目瞭然,一看就知道filter中的ATTR和TAG對應了match分片語中的type類型,所以filter對應了非關係型分詞的matcher函數。

 

    addCombinator

function addCombinator(matcher){    return function(elem, context, xml){        while(elem = elem["parentNode"]){            if(elem.nodeType === 1)                //找到第一個親密的節點,立馬就用終極匹配器判斷這個節點是否符合前面的規則                return matcher(elem);        }    }}

    addCombinator對應關係型分詞matcher。本例只列舉了祖先和子孫關係" "的合并,返回的結果是一個簽名為function(elem, contenxt, xml)的函數,函數中elem[“parentNode”]找到文件項目類型的父節點,再調用matcher校正這個父節點是否匹配。
    所以關係型matcher函數執行的過程:先通過關聯類型找到匹配元素,然後再調用matcher校正匹配結果。

 

    elementMatcher

function elementMatcher(matchers){    return matchers.length > 1 ?        function(elem, context, xml){            var i = matchers.length;            while(i--){                if(!matchers[i](elem, context, xml)){                    return false;                }            }            return true;        }:        matchers[0];}

    elementMatcher()函數就是幹實事的傢伙,它遍曆matchers函數數組,執行每個matcher函數,一旦有matchers[i]返回false則整個匹配就失敗了。這裡需要注意的一點是i--,為什麼是反序遍曆?因為JQuery Sizzle匹配的原則是從右往左。由於前面的match數組是按照選取器從左往右儲存的,所以這裡先執行最後面的。

    上面所有的代碼只是簡單類比了JQuery Sizzle引擎的執行過程,真實的原始碼很複雜,估計只有大神些才能領悟透徹。大神,艾倫得算一個。

    說到閉包,如果能把JQuery Sizzle程式碼分析透徹,理解閉包易如反掌。本篇介紹的函數傳回值都是函數,而每個返回函數需要的變數都是通過閉包儲存起來,在真正執行函數的時候再讀取這些變數。

 

   如果本篇內容對大家有協助,請點擊頁面右下角的關注。如果覺得不好,也歡迎拍磚。你們的評價就是博主的動力!下篇內容,敬請期待!

JQuery Sizzle引擎原始碼分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.