Sizzle源碼分析 (一)

Sizzle 源碼分析 (一)

2.1 穩定 版本
Sizzle 選擇器引擎博大精深,下面開始閱讀它的源代碼,並從中作出標記 。先從入口開始,以後慢慢切入 。javascript

入口函數 Sizzle ()

源碼 194-301 行java

function Sizzle(selector, context, results, seed) {
    var match, elem, m, nodeType,
        i, groups, old, nid, newContext, newSelector;


    //37 行, preferredDoc = window.document,
    // 這裏防止document被重寫。
    // 若是了上下文對象的ownerDocument || preferredDoc 不等於document,覆寫全部涉及到document的內部方法
    // 在這裏傳遞參數 context,即覆寫 context.ownerDocument || preferredDoc 的方法 。
    // 469-838 setDocument()
    // 在setDocument中,若是  context存在,那麼就設置覆寫 context.ownerDocument ,不然覆寫  preferredDoc
    if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
        setDocument(context);
    }

    context = context || document;
    results = results || [];

    // 若是不是字符串,或者爲null或undefined,即當即返回
    if (!selector || typeof selector !== "string") {
        return results;
    }
    //若是上下文不是dom元素,document,fragment文檔片斷之一,那麼返回空數組
    if ((nodeType = context.nodeType) !== 1 && nodeType !== 9 && nodeType !== 11) {
        return [];
    }
    // documentIsHTML 表示爲HTML文檔, seed表示種子元素 。
    //  若是給出了種子元素,那麼直接跳到過濾步驟 再也不快速查找
    if (documentIsHTML && !seed) {

        //不能在文檔片斷下使用快速匹配,由於文檔片斷再也不document節點下, 而是在本身的節點下 ,使用fragmentDocument.getE... 查找.
        if (nodeType !== 11 && (match = rquickExpr.exec(selector))) {
            // 快速匹配正則表達式 135 行, rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,  
            if ((m = match[1])) {
                // id 選擇器
                if (nodeType === 9) {  // 若是上下文是document 
                    elem = context.getElementById(m);
                    // 防止藍莓 4.6 版本,還會選擇到已經不在document樹中的元素 。
                    if (elem && elem.parentNode) {
                        // 防止IE,Opera,Webkit 低版本 會返回name 也是這個選擇器的元素。
                        if (elem.id === m) {
                            results.push(elem);
                            return results;
                        }
                    } else {
                        // 若是父元素不存在,說明已經不在DOM樹中了 。
                        return results;
                    }
                } else {
                    // 若是上下文不是document,那麼要調用 document的方法,由於普通元素沒有getElementById 方法 。由於在前面setDocument已經保證過 context.ownerDocument 必定包含原生document的方法。
                    if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) &&
                        contains(context, elem) && elem.id === m) {
                        // 716 行,contains 方法保證了 elem 必定位於context的dom樹內。
                        results.push(elem);
                        return results;
                     }
                }
            } else if (match[2]) {
                // Tag 選擇器
                // 這裏必定要用apply,由於  context.getElementsByTagName(selector) 是一個類數組,經過 apply將其解構 。
                push.apply(results, context.getElementsByTagName(selector));
                return results;


            } else if ((m = match[3]) && support.getElementsByClassName) {
                // 類選擇器, apply原理同上 。
                //  521 行檢測,support 是作功能檢測 。 <IE9 不支持 getElementsByClassName 
                push.apply(results, context.getElementsByClassName(m));
                return results;
            }
        }

        // 若是隻是 querySelector ,那麼直接調用這個接口  rbuggyQSA檢測是否存在bug
        // 若是 rbuggyQSA 不存在bug,就可使用
        // 若是存在bug,可是選擇器中沒有包含bug 的部分。
        if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) {
            // 隨機數
            nid = old = expando;
            // 上下文 
            newContext = context;
            // querySelectorAll實現存在BUG,它會在包含本身的集合內查找符合本身的元素節點。 可是根據規範,應該是當前上下文的全部子孫下找,
            newSelector = nodeType !== 1 && selector;
            // 若是上下文是 Element,就要處理 querySelectorAll的 BUG 。
            if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") {
                groups = tokenize(selector);
                // 若是存在ID,則將ID取得出來放到這個分組的最前面,好比div b --> [id=xxx] div b
                // 不存在ID,就建立一個ID,重複上面的操做,但最後會刪掉此ID

                // 這裏的實現實際上是很是巧妙的 。

                // 舉一個例子,存在選擇器 .pawn span 。而且上下文是 document.querySelector(".pawn") 。
                // 因爲 querySelectorAll的 bug,查找的時候依舊會查找到span,可是根據原則,應當在.pawn 的下面查找,也就是不能容許span 。
                // 做者想到一種辦法,給 .pawn 再加一個id,好比 :shit123,那麼選擇器變爲  #shit123 .pawn span。
                // 可是,#shit123 和 .pawn 是同級的,#shit123 .pawn 選擇器就存在矛盾,便沒法選擇到span。
                // 把選擇器重組以後,再將父元素向上提一層 。 
                //這樣並不會形成遺漏,由於若是 選擇器最高層爲context,按照要求沒法選中,重組以後又矛盾,天然沒法選中 ,若是最高層不是context,那麼外面包一層context,再將父元素向上提一層也無妨 。



                if ((old = context.getAttribute("id"))) {
                    //138行, 從新轉義字符 rescape = /'|\\/g,
                    nid = old.replace(rescape, "\\$&");
                } else {
                    //   若是沒有id,則設置id 。
                    context.setAttribute("id", nid);
                }
                //  ID 屬性選擇器
                nid = "[id='" + nid + "'] ";

                i = groups.length;
                while (i--) {
                    groups[i] = nid + toSelector(groups[i]);
                }
                // 137 行, rsibling = /[+~]/,
                //   function testContext(context) {
                // return context && typeof context.getElementsByTagName !== "undefined" && context;
                // }
                // IE8下若是元素節點爲Object,沒法找到元素 。所以上下文若是是Object節點,那麼向上提一層
                newContext = rsibling.test(selector) && testContext(context.parentNode) || context;
                newSelector = groups.join(",");
            }
            // 若是上下文是 元素,而且是Object,那麼 newSelector 爲假,直接跳過此步驟。
            // IE<9 在object下獲取不到元素 。
            if (newSelector) {
                try {
                    push.apply(results,
                        newContext.querySelectorAll(newSelector)
                    );
                    return results;
                } catch (qsaError) {
                } finally {
                    if (!old) {
                        context.removeAttribute("id");
                    }
                }
            }
        }
    }

    // 
    // 其餘複雜的狀況就調用select方法進行選擇
    // whitespace = "[\\x20\\t\\r\\n\\f]",
    //經過replace 去掉兩邊的空格進行查找 。
    //  103行,   rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),
    return select(selector.replace(rtrim, "$1"), context, results, seed);
}
相關文章
相關標籤/搜索