Sizzle1.9的源碼分析


沒有完全看明白,看來得從Sizzle1.8看起。這是Sizzle又一個分水嶺,引入了編譯函數機制。

(function(window, undefined) {

    var i,
    cachedruns,
    Expr,
    getText,
    isXML,
    compile,
    outermostContext,
    recompare,
    sortInput,
    // Local document vars
    setDocument,
    document,
    docElem,
    documentIsHTML,
    rbuggyQSA,
    rbuggyMatches,
    matches,
    contains,
    // Instance-specific data
    expando = "sizzle" + -(new Date()),
        preferredDoc = window.document,
        support = {},
        dirruns = 0,
        done = 0,
        classCache = createCache(),
        tokenCache = createCache(),
        compilerCache = createCache(),
        hasDuplicate = false,
        sortOrder = function() {
            return 0;
        },
        // General-purpose constants
        strundefined = typeof undefined,
        MAX_NEGATIVE = 1 << 31,
        // Array methods
        arr = [],
        pop = arr.pop,
        push_native = arr.push,
        push = arr.push,
        slice = arr.slice,
        //用於判定元素是否在此數組內
        indexOf = arr.indexOf || function(elem) {
            var i = 0,
                len = this.length;
            for (; i < len; i++) {
                if (this[i] === elem) {
                    return i;
                }
            }
            return -1;
        },
        // Regular expressions

        // 空白的正則 http://www.w3.org/TR/css3-selectors/#whitespace
        whitespace = "[\\x20\\t\\r\\n\\f]",
        // 類選擇器與標簽選擇器的正則 http://www.w3.org/TR/css3-syntax/#characters
        characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
        //ID選擇器與標簽選擇器的正則,HTML5將ID的規則放寬,允許ID可以是純數字
        // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
        // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
        identifier = characterEncoding.replace("w", "w#"),
        // 屬性選擇器的操作符 http://www.w3.org/TR/selectors/#attribute-selectors
        operators = "([*^$|!~]?=)",
        //屬性選擇器的正則
        attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
            "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
        // 偽類的正則
        pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace(3, 8) + ")*)|.*)\\)|)",
        // 去掉兩端空白正則
        rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),
        //並聯選擇器的正則
        rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"),
        //關系選擇器的正則
        rcombinators = new RegExp("^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*"),
        rpseudo = new RegExp(pseudos),
        ridentifier = new RegExp("^" + identifier + "$"),
        matchExpr = {
            "ID": new RegExp("^#(" + characterEncoding + ")"),
            "CLASS": new RegExp("^\\.(" + characterEncoding + ")"),
            "NAME": new RegExp("^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]"),
            "TAG": new RegExp("^(" + characterEncoding.replace("w", "w*") + ")"),
            "ATTR": new RegExp("^" + attributes),
            "PSEUDO": new RegExp("^" + pseudos),
            //子元素過濾偽類
            "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
                "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
                "*(\\d+)|))" + whitespace + "*\\)|)", "i"),
            // 位置偽類
            "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i")
        },
        rsibling = /[\x20\t\r\n\f]*[+~]/,
        //取函數的toString判定是否原生API,比如一些庫偽造了getElementsByClassName
        rnative = /^[^{]+\{\s*\[native code/,
        //判定選擇符是否為單個ID選擇器,或標簽選擇器或類選擇器
        rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
        //jQuery自定義的input偽類
        rinputs = /^(?:input|select|textarea|button)$/i,
        //jQuery自定義的header偽類
        rheader = /^h\d$/i,
        rescape = /'|\\/g,
        //用於匹配不帶引號的屬性值,用於matchesSelector
        rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
        // CSS 字符轉義 http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
        runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
        funescape = function(_, escaped) {
            var high = "0x" + escaped - 0x10000;
            // NaN means non-codepoint
            return high !== high ? escaped :
            // BMP codepoint
            high < 0 ? String.fromCharCode(high + 0x10000) :
            // Supplemental Plane codepoint (surrogate pair)
            String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);
        };

    //  將一個NodeList轉換為一個純數組
    try {
        push.apply(
        (arr = slice.call(preferredDoc.childNodes)),
        preferredDoc.childNodes);
        // Support: Android<4.0
        // Detect silently failing push.apply
        arr[preferredDoc.childNodes.length].nodeType;
    } catch (e) {
        push = {
            apply: arr.length ?
            // Leverage slice if possible

            function(target, els) {
                push_native.apply(target, slice.call(els));
            } :
            // IE6-8只能逐個遍歷加入

            function(target, els) {
                var j = target.length,
                    i = 0;
                // Can't trust NodeList.length
                while ((target[j++] = els[i++])) {}
                target.length = j - 1;
            }
        };
    }

    function isNative(fn) {
        return rnative.test(fn + "");
    }


    //創建一個緩存函數,它以自己為儲存倉庫,鍵名放到閉包內的一個數組內,當數組的個數超過
    //Expr.cacheLength時,則去掉最前面的鍵值

    function createCache() {
        var cache,
        keys = [];
        return (cache = function(key, value) {
            //對鍵名進行改造,防止與Object.prototype的原生方法重名,比如toString, valueOf th native prototype properties (see Issue #157)
            if (keys.push(key += " ") > Expr.cacheLength) {
                // Only keep the most recent entries
                delete cache[keys.shift()];
            }
            return (cache[key] = value);
        });
    }

    /**
     * Mark a function for special use by Sizzle
     * @param {Function} fn The function to mark
     */

    function markFunction(fn) {
        fn[expando] = true;
        return fn;
    }


    //用於做各種特征檢測,比如是否支持某個API,API支持是否完美

    function assert(fn) {
        var div = document.createElement("div");

        try {
            return !!fn(div);
        } catch (e) {
            return false;
        } finally {
            // release memory in IE
            div = null;
        }
    }
    //Sizzle主函數被設計得能遞歸自身,參數依次是選擇符,上下文對象,結果集,種子集
    //除了第一個參數,其他都是可選,上下文對象默認是當前文檔對象

    function Sizzle(selector, context, results, seed) {
        var match, elem, m, nodeType,
        // QSA vars
        i, groups, old, nid, newContext, newSelector;
        //如果指定了上下文對象的ownerDocument不等於當前文檔對象,覆寫所有涉及到document的內部方法
        if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
            setDocument(context);
        }

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

        if (!selector || typeof selector !== "string") {
            return results;
        }

        if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {
            return [];
        }

        if (documentIsHTML && !seed) {

            // Shortcuts
            if ((match = rquickExpr.exec(selector))) {
                // Speed-up: Sizzle("#ID")
                //如果只有一個ID,那么直接getElementById,然后判定其是否在DOM樹,
                //元素的ID確實為目標值就將它合並到結果集中
                if ((m = match[1])) {
                    if (nodeType === 9) {
                        elem = context.getElementById(m);
                        // Check parentNode to catch when Blackberry 4.6 returns
                        // nodes that are no longer in the document #6963
                        if (elem && elem.parentNode) {
                            // Handle the case where IE, Opera, and Webkit return items
                            // by name instead of ID
                            if (elem.id === m) {
                                results.push(elem);
                                return results;
                            }
                        } else {
                            return results;
                        }
                    } else {
                        //如果上下文非文檔對象,需要用contains函數進行驗證
                        if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) && contains(context, elem) && elem.id === m) {
                            results.push(elem);
                            return results;
                        }
                    }

                    //如果選擇符為一個標簽選擇器
                } else if (match[2]) {
                    push.apply(results, context.getElementsByTagName(selector));
                    return results;

                    //如果選擇符為一個類選擇器,瀏覽器又支持getElementsByClassName
                } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) {
                    push.apply(results, context.getElementsByClassName(m));
                    return results;
                }
            }

            // 嘗試使用querySelectorAll,並且選擇符不存在那些有問題的選擇器
            if (!support.qsa && !rbuggyQSA.test(selector)) {
                old = true;
                nid = expando;
                newContext = context;
                newSelector = nodeType === 9 && selector;
                //IE8的querySelectorAll實現存在BUG,它會在包含自己的集合內查找符合自己的元素節點
                //根據規范,應該是當前上下文的所有子孫下找
                //IE8下如果元素節點為Object,無法找到元素

                if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") {
                    groups = tokenize(selector); //將選擇符細分N個分組
                    //如果存在ID,則將ID取得出來放到這個分組的最前面,比如div b --> [id=xxx] div b
                    //不存在ID,就創建一個ID,重復上面的操作,但最后會刪掉此ID
                    if ((old = context.getAttribute("id"))) {
                        nid = old.replace(rescape, "\\$&");
                    } else {
                        context.setAttribute("id", nid);
                    }
                    nid = "[id='" + nid + "'] ";

                    i = groups.length;
                    while (i--) {
                        groups[i] = nid + toSelector(groups[i]);
                    }
                    newContext = rsibling.test(selector) && context.parentNode || context;
                    newSelector = groups.join(",");
                }

                if (newSelector) {
                    try {
                        push.apply(results,
                        newContext.querySelectorAll(newSelector));
                        return results;
                    } catch (qsaError) {} finally {
                        if (!old) {
                            context.removeAttribute("id");
                        }
                    }
                }
            }
        }

        // 否則去掉兩邊的空白開始查找
        return select(selector.replace(rtrim, "$1"), context, results, seed);
    }

    /**
     * Detect xml
     * @param {Element|Object} elem An element or a document
     */
    isXML = Sizzle.isXML = function(elem) {
        // documentElement is verified for cases where it doesn't yet exist
        // (such as loading iframes in IE - #4833)
        var documentElement = elem && (elem.ownerDocument || elem).documentElement;
        return documentElement ? documentElement.nodeName !== "HTML" : false;
    };

    /**
     * Sets document-related variables once based on the current document
     * @param {Element|Object} [doc] An element or document object to use to set the document
     * @returns {Object} Returns the current document
     */
    setDocument = Sizzle.setDocument = function(node) {
        var doc = node ? node.ownerDocument || node : preferredDoc;
        //如果文檔對象等於當前文檔對象,無法確認其文檔對象,或沒有HTML,直接返回
        //此情況出現機率接近零
        if (doc === document || doc.nodeType !== 9 || !doc.documentElement) {
            return document;
        }

        // Set our document
        document = doc;
        docElem = doc.documentElement;

        // 是否為HTML文檔
        documentIsHTML = !isXML(doc);

        //判定getElementsByTagName("*")是否只返回元素節點,IE6-8會混雜注釋節點
        support.getElementsByTagName = assert(function(div) {
            div.appendChild(doc.createComment(""));
            return !div.getElementsByTagName("*").length;
        });
        //判定瀏覽器是否區分property與attribute,比如下面測試,multiple為select的一個property
        //只能通過el.xxx這樣的方法去取值,若使用getAttribute去取會返回null
        support.attributes = assert(function(div) {
            div.innerHTML = "<select></select>";
            var type = typeof div.lastChild.getAttribute("multiple");
            // IE8 returns a string for some attributes even when not present
            return type !== "boolean" && type !== "string";
        });

        //判定getElementsByClassName值得信任,比如下面測試發現opera9.6不支持第二個類名,
        //safari3.2緩存過度,忘了更新自身
        support.getElementsByClassName = assert(function(div) {
            div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
            if (!div.getElementsByClassName || !div.getElementsByClassName("e").length) {
                return false; //opera9.6
            }
            // Safari 3.2 
            div.lastChild.className = "e";
            return div.getElementsByClassName("e").length === 2;
        });

        // 判定getElementsByName是否可用Check if getElementsByName privileges form controls or returns elements by ID
        // If so, assume (for broader support) that getElementById returns elements by name
        support.getByName = assert(function(div) {
            div.id = expando + 0; //用於檢測是否區分name與ID
            // getElementsByName是一個問題多的API,Sizzle原來的注釋透露多少東西,
            //因此我在這里補上
            //1 IE6-7下getElementsByName與getElementById都不區分元素的name與ID
            //2 IE的getElementsByName只對表單元素有效,無視擁有相同name值的span div元素
            //3 IE6-7下即使通過document.createElement創建一個表單元素,動態設置name與插入
            //  DOM樹,getElementsByName無法找到此元素,innerHTML也不行。一定需要以
            //   document.createElement("<input name="aaa"/>")方式生成元素才行。
            //   同樣的情況也發生在iframe上,IE6-7的iframe的name也需要這樣同時生成。
            //4 name本來是一個property,但標准瀏覽器好像已經默認setAttribute("name","xxx")
            //  也能被getElementsByName獲取到。
            //5 IE6-8通過innerHTML生成包含name屬性的元素時,可能發生無法捕獲的錯誤
            div.appendChild(document.createElement("a")).setAttribute("name", expando);
            div.appendChild(document.createElement("i")).setAttribute("name", expando);
            docElem.appendChild(div);

            // Test
            var pass = doc.getElementsByName &&
            // buggy browsers will return fewer than the correct 2
            doc.getElementsByName(expando).length === 2 +
            // buggy browsers will return more than the correct 0
            doc.getElementsByName(expando + 0).length;

            // Cleanup
            docElem.removeChild(div);

            return pass;
        });

        // Support: Webkit<537.32
        // 判定compareDocumentPosition是否可靠
        support.sortDetached = assert(function(div1) {
            return div1.compareDocumentPosition &&
            // Should return 1, but Webkit returns 4 (following)
            (div1.compareDocumentPosition(document.createElement("div")) & 1);
        });

        // 調整IE6-7下獲取某些屬性的方式,這里只對href, type進行處理
        //實際上,在jQuery里它會被jQuery.attr覆蓋
        Expr.attrHandle = assert(function(div) {
            div.innerHTML = "<a href='#'></a>";
            //IE取href, action, src時不會原型返回,會返回其絕對路徑
            return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && div.firstChild.getAttribute("href") === "#";
        }) ? {} : {
            "href": function(elem) {
                return elem.getAttribute("href", 2);
            },
            "type": function(elem) {
                return elem.getAttribute("type");
            }
        };

        // ID find and filter
        if (support.getByName) {
            Expr.find["ID"] = function(id, context) {
                //Blackberry 4.6 緩存過度,即便這元素被移出DOM樹也能找到
                if (typeof context.getElementById !== strundefined && documentIsHTML) {
                    var m = context.getElementById(id);
                    return m && m.parentNode ? [m] : [];
                }
            };
            Expr.filter["ID"] = function(id) { //過濾器
                var attrId = id.replace(runescape, funescape);
                return function(elem) {
                    return elem.getAttribute("id") === attrId;
                };
            };
        } else {
            Expr.find["ID"] = function(id, context) {
                if (typeof context.getElementById !== strundefined && documentIsHTML) {
                    var m = context.getElementById(id);
                    //1 IE6-7無法區分name與ID,
                    //2 如果form元素的ID為aaa,它下面也有一個name為aaa的INPUT元素,會錯誤返回INPUT
                    return m ? m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? [m] : undefined : [];
                }
            };
            Expr.filter["ID"] = function(id) {
                var attrId = id.replace(runescape, funescape);
                return function(elem) {
                    var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
                    return node && node.value === attrId;
                };
            };
        }

        // Tag
        Expr.find["TAG"] = support.getElementsByTagName ? function(tag, context) {
            if (typeof context.getElementsByTagName !== strundefined) {
                return context.getElementsByTagName(tag);
            }
        } : function(tag, context) {
            var elem,
            tmp = [],
                i = 0,
                results = context.getElementsByTagName(tag);

            // 過濾注釋節點
            if (tag === "*") {
                while ((elem = results[i++])) {
                    if (elem.nodeType === 1) {
                        tmp.push(elem);
                    }
                }

                return tmp;
            }
            return results;
        };

        // Name
        Expr.find["NAME"] = support.getByName && function(tag, context) {
            if (typeof context.getElementsByName !== strundefined) {
                return context.getElementsByName(name);
            }
        };

        // Class
        Expr.find["CLASS"] = support.getElementsByClassName && function(className, context) {
            if (typeof context.getElementsByClassName !== strundefined && documentIsHTML) {
                return context.getElementsByClassName(className);
            }
        };


        rbuggyMatches = [];

        rbuggyQSA = [":focus"];
        //querySelectorAll可以說得上兼容性最差的API,每個瀏覽器每個版本可能都有差異
        //IE8只支持到CSS2,
        //對於focus偽類,其實現在除opera,safari外,其他瀏覽器取:focus都正常,
        //但這個東西實在很難在程序來偵測,jQuery選擇了一刀切
        if ((support.qsa = isNative(doc.querySelectorAll))) {
            assert(function(div) {
                //對於布爾屬性,只要是顯式設置了無論值是什么,selected都為false,且通過屬性選擇器都能選取到
                //這個在IE下有BUG
                div.innerHTML = "<select><option selected=''></option></select>";
                //如果為零,則把常見的布爾屬性都放到buggy列表中
                // IE8 - Some boolean attributes are not treated correctly
                if (!div.querySelectorAll("[selected]").length) {
                    rbuggyQSA.push("\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)");
                }
                //根據W3C標准,:checked應該包含被選中的option元素,IE8失敗
                // Webkit/Opera - :checked should return selected option elements
                // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
                if (!div.querySelectorAll(":checked").length) {
                    rbuggyQSA.push(":checked");
                }
            });

            assert(function(div) {
                //如果屬性值為空字符串,那么對於^= $= *=等操作符直接返回false,不會被匹配,opera10-12/IE8都不正確
                div.innerHTML = "<input type='hidden' i=''/>";
                if (div.querySelectorAll("[i^='']").length) {
                    rbuggyQSA.push("[*^$]=" + whitespace + "*(?:\"\"|'')");
                }
                //firefox3.5無法對隱藏元素取:enabled/:disabled偽類,而IE8則直接拋錯
                if (!div.querySelectorAll(":enabled").length) {
                    rbuggyQSA.push(":enabled", ":disabled");
                }

                // Opera 10-11對於非法偽類不會拋錯 
                div.querySelectorAll("*,:x");
                rbuggyQSA.push(",.*:");
            });
        }
        //判定是否支持matchesSelector,如果不支持,看它是否存在帶私有前綴的近親
        if ((support.matchesSelector = isNative((matches = docElem.matchesSelector || docElem.mozMatchesSelector || docElem.webkitMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector)))) {

            assert(function(div) {
                //IE9緩存過度,能匹配移出DOM樹的節點
                support.disconnectedMatch = matches.call(div, "div");
                //Gecko對於非法選擇符不會拋錯,而是返回false
                matches.call(div, "[s!='']:x");
                rbuggyMatches.push("!=", pseudos);
            });
        }

        rbuggyQSA = new RegExp(rbuggyQSA.join("|"));
        rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|"));
        //重寫contains,有原生API就用原生API,否則就遍歷DOM樹
        contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? function(a, b) {
            var adown = a.nodeType === 9 ? a.documentElement : a,
                bup = b && b.parentNode;
            return a === bup || !! (bup && bup.nodeType === 1 && (
            adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16));
        } : function(a, b) {
            if (b) {
                while ((b = b.parentNode)) {
                    if (b === a) {
                        return true;
                    }
                }
            }
            return false;
        };

        // 重寫比較函數
        sortOrder = function(a, b) {
            //略
        }
    };
    //判定這些元素是否匹配expr
    Sizzle.matches = function(expr, elements) {
        return Sizzle(expr, null, null, elements);
    };

    Sizzle.matchesSelector = function(elem, expr) {
        // Set document vars if needed
        if ((elem.ownerDocument || elem) !== document) {
            setDocument(elem);
        }
        //IE9的querySelectorAll要求屬性選擇器的值必須被引起來
        expr = expr.replace(rattributeQuotes, "='$1']");
        //優化使用原生API
        if (support.matchesSelector && documentIsHTML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr)) {
            try {
                var ret = matches.call(elem, expr);
                if (ret || support.disconnectedMatch || elem.document && elem.document.nodeType !== 11) {
                    return ret;
                }
            } catch (e) {}
        }

        return Sizzle(expr, document, null, [elem]).length > 0;
    };

    Sizzle.contains = function(context, elem) {
        // Set document vars if needed
        if ((context.ownerDocument || context) !== document) {
            setDocument(context);
        }
        return contains(context, elem);
    };

    Sizzle.attr = function(elem, name) {
        var val;

        // Set document vars if needed
        if ((elem.ownerDocument || elem) !== document) {
            setDocument(elem);
        }
        //HTML統一對屬性名小寫化
        if (documentIsHTML) {
            name = name.toLowerCase();
        }
        if ((val = Expr.attrHandle[name])) {
            return val(elem);
        }
        if (!documentIsHTML || support.attributes) {
            return elem.getAttribute(name);
        }
        return ((val = elem.getAttributeNode(name)) || elem.getAttribute(name)) && elem[name] === true ? name : val && val.specified ? val.value : null;
    };

    Sizzle.error = function(msg) {
        throw new Error("Syntax error, unrecognized expression: " + msg);
    };

    Sizzle.uniqueSort = function(results) {
        //去重排序
        var elem,
        duplicates = [],
            j = 0,
            i = 0;

        // Unless we *know* we can detect duplicates, assume their presence
        hasDuplicate = !support.detectDuplicates;
        // Compensate for sort limitations
        recompare = !support.sortDetached;
        sortInput = !support.sortStable && results.slice(0);
        results.sort(sortOrder);

        if (hasDuplicate) {
            while ((elem = results[i++])) {
                if (elem === results[i]) {
                    j = duplicates.push(i);
                }
            }
            while (j--) {
                results.splice(duplicates[j], 1);
            }
        }

        return results;
    };


    function siblingCheck(a, b) {
        //比較同一個父親下,兩個元素節點的先后順序
        var cur = b && a,
            diff = cur && (~b.sourceIndex || MAX_NEGATIVE) - (~a.sourceIndex || MAX_NEGATIVE);

        // Use IE sourceIndex if available on both nodes
        if (diff) {
            return diff;
        }
        // Check if b follows a
        if (cur) {
            while ((cur = cur.nextSibling)) {
                if (cur === b) {
                    return -1;
                }
            }
        }

        return a ? 1 : -1;
    }

    //創建一個偽類的過濾函數,此方法是根據表單元素的type值生成
    //比如:radio, :text, :checkbox, :file, :image等自定義偽類

    function createInputPseudo(type) {
        return function(elem) {
            var name = elem.nodeName.toLowerCase();
            return name === "input" && elem.type === type;
        };
    }

    //創建一個偽類的過濾函數,此方法是根據表單元素的type值或標簽類型生成
    //如果:button, :submit自定義偽類

    function createButtonPseudo(type) {
        return function(elem) {
            var name = elem.nodeName.toLowerCase();
            return (name === "input" || name === "button") && elem.type === type;
        };
    }

    //用於創建位置偽類的過濾函數,它們是模擬從左向右的順序進行選擇,
    //匹配到它時的結果集的位置來挑選元素的
    //比如:odd,:even, :eq, :gt, :lt, :first, :last

    function createPositionalPseudo(fn) {
        return markFunction(function(argument) {
            argument = +argument;
            return markFunction(function(seed, matches) {
                var j,
                matchIndexes = fn([], seed.length, argument),
                    i = matchIndexes.length;

                while (i--) {
                    if (seed[(j = matchIndexes[i])]) {
                        seed[j] = !(matches[j] = seed[j]);
                    }
                }
            });
        });
    }

    /**
     * Utility function for retrieving the text value of an array of DOM nodes
     * @param {Array|Element} elem
     */
    //用於:contains偽類,用於匹配當前元素的textConten是否包含目標字符串
    //但對於XML,則需要逐個取得它里面的文本節點,CDATA節點的nodeValue進行拼接了
    getText = Sizzle.getText = function(elem) {
        var node,
        ret = "",
            i = 0,
            nodeType = elem.nodeType;

        if (!nodeType) {
            // If no nodeType, this is expected to be an array
            for (;
            (node = elem[i]); i++) {
                // Do not traverse comment nodes
                ret += getText(node);
            }
        } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) {

            if (typeof elem.textContent === "string") {
                return elem.textContent;
            } else {
                // Traverse its children
                for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
                    ret += getText(elem);
                }
            }
        } else if (nodeType === 3 || nodeType === 4) {
            return elem.nodeValue;
        }
        // Do not include comment or processing instruction nodes

        return ret;
    };

    Expr = Sizzle.selectors = {
        // Can be adjusted by the user
        cacheLength: 50,
        createPseudo: markFunction,
        match: matchExpr,
        find: {},
        relative: {
            ">": {
                dir: "parentNode",
                first: true
            },
            " ": {
                dir: "parentNode"
            },
            "+": {
                dir: "previousSibling",
                first: true
            },
            "~": {
                dir: "previousSibling"
            }
        },
        preFilter: {
            //預處理,有的選擇器,比如屬性選擇器與偽類從選擇器組分割出來,還要再細分
            //屬性選擇器要切成屬性名,屬性值,操作符;偽類要切為類型與傳參;
            //子元素過濾偽類還要根據an+b的形式再划分
            "ATTR": function(match) {
                match[1] = match[1].replace(runescape, funescape);

                // Move the given value to match[3] whether quoted or unquoted
                match[3] = (match[4] || match[5] || "").replace(runescape, funescape);

                if (match[2] === "~=") {
                    match[3] = " " + match[3] + " ";
                }

                return match.slice(0, 4);
            },
            "CHILD": function(match) {
                //將它的偽類名稱與傳參拆分為更細的單元,以數組形式返回
                //比如 ":nth-child(even)"變為
                //["nth","child","even", 2, 0, undefined, undefined, undefined]
            },
            "PSEUDO": function(match) {
                //將它的偽類名稱與傳參進行再處理
                //比如:contains偽類會去掉兩邊的引號,反義偽類括號部分會再次提取
            }
        },
        filter: {
            //過濾函數(它們基本上都是curry)
            "TAG": function(nodeName) { //標簽選擇器
                if (nodeName === "*") {
                    return function() {
                        return true;
                    };
                }

                nodeName = nodeName.replace(runescape, funescape).toLowerCase();
                return function(elem) {
                    return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
                };
            },
            "CLASS": function(className) { //類選擇器,創建一個正則進行匹配
                var pattern = classCache[className + " "];

                return pattern || (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && classCache(className, function(elem) {
                    return pattern.test(elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "");
                });
            },
            "ATTR": function(name, operator, check) {
                return function(elem) { //屬性選擇器
                    var result = Sizzle.attr(elem, name);

                    if (result == null) {
                        return operator === "!=";
                    }
                    if (!operator) {
                        return true;
                    }

                    result += "";
                    //這里的三目運算符套嵌得非常復雜,可以參看一下EXT的DOMQuery或mass的Icarus的實現
                    return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf(check) === 0 : operator === "*=" ? check && result.indexOf(check) > -1 : operator === "$=" ? check && result.slice(-check.length) === check : operator === "~=" ? (" " + result + " ").indexOf(check) > -1 : operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : false;
                };
            },
            "CHILD": function(type, what, argument, first, last) {
                //這里處理子元素過濾偽類,如:nth-child, :first-child, :only-child
            },
            "PSEUDO": function(pseudo, argument) {
                //這里各種偽類,分派給上方的"CHILD"與下方的"pseudos"去處理
            }
        },
        pseudos: {
            //這里包含各種偽類的過濾函數
            //如:not,:lang,:target,:root,:enabled,:disabled,:checked, :empty(原生偽類)
            //parent, :has,:contains(源自xpath的自定義偽類)
            // :header,:input,:text,:radio,:checkbox,:submit,:reset,
            // :file,:password,:image(自定義標簽偽類)
            // :last,:first,:last, :eq, :even, :odd, :lt,:gt(自定義位置偽類)
        }
    };


    function tokenize(selector, parseOnly) {
        var matched, match, tokens, type,
        soFar, groups, preFilters,
        cached = tokenCache[selector + " "];
//先查看緩存了沒有
        if (cached) {
            return parseOnly ? 0 : cached.slice(0);
        }
        soFar = selector;
        groups = [];//這是最后要返回的結果,一個二維數組
        //比如"title,div > :nth-child(even)"解析下面的符號流
        // [ [{value:"title",type:"TAG",matches:["title"]}],
        //   [{value:"div",type:["TAG",matches:["div"]},
        //    {value:">", type: ">"},
        //    {value:":nth-child(even)",type:"CHILD",matches:["nth",
        //     "child","even",2,0,undefined,undefined,undefined]}
        //   ] 
        // ]
        //有多少個並聯選擇器,里面就有多少個數組,數組里面是擁有value與type的對象
        preFilters = Expr.preFilter;
        while (soFar) {
            // 以第一個逗號切割選擇符,然后去掉前面的部分
            if (!matched || (match = rcomma.exec(soFar))) {
                if (match) {
                    // Don't consume trailing commas as valid
                    soFar = soFar.slice(match[0].length) || soFar;
                }
                groups.push(tokens = []);
            }

            matched = false;
            //將剛才前面的部分以關系選擇器再進行划分
            if ((match = rcombinators.exec(soFar))) {
                matched = match.shift();
                tokens.push({
                    value: matched,
                    // Cast descendant combinators to space
                    type: match[0].replace(rtrim, " ")
                });
                soFar = soFar.slice(matched.length);
            }
            //將每個選擇器組依次用ID,TAG,CLASS,ATTR,CHILD,PSEUDO這些正則進行匹配
            for (type in Expr.filter) {//preFilters是用於分析選擇器的名字與參數
                if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || (match = preFilters[type](match)))) {
                    matched = match.shift();
                    tokens.push({
                        value: matched,
                        type: type,
                        matches: match
                    });
                    soFar = soFar.slice(matched.length);
                }
            }
            if (!matched) {
                break;
            }
        }
        return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :
        // 放到tokenCache函數里進行緩存
        tokenCache(selector, groups).slice(0);
    }

    function toSelector(tokens) {
        var i = 0,//將符合流重新組合成選擇符,這時能把多余的空白去掉
            len = tokens.length,
            selector = "";
        for (; i < len; i++) {
            selector += tokens[i].value;
        }
        return selector;
    }

    function addCombinator(matcher, combinator, base) {
        var dir = combinator.dir,
            checkNonElements = base && dir === "parentNode",
            doneName = done++;

        return combinator.first ?
        // Check against closest ancestor/preceding element

        function(elem, context, xml) {
            while ((elem = elem[dir])) {
                if (elem.nodeType === 1 || checkNonElements) {
                    return matcher(elem, context, xml);
                }
            }
        } :
        // Check against all ancestor/preceding elements

        function(elem, context, xml) {
            var data, cache, outerCache,
            dirkey = dirruns + " " + doneName;

            // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
            if (xml) {
                while ((elem = elem[dir])) {
                    if (elem.nodeType === 1 || checkNonElements) {
                        if (matcher(elem, context, xml)) {
                            return true;
                        }
                    }
                }
            } else {
                while ((elem = elem[dir])) {
                    if (elem.nodeType === 1 || checkNonElements) {
                        outerCache = elem[expando] || (elem[expando] = {});
                        if ((cache = outerCache[dir]) && cache[0] === dirkey) {
                            if ((data = cache[1]) === true || data === cachedruns) {
                                return data === true;
                            }
                        } else {
                            cache = outerCache[dir] = [dirkey];
                            cache[1] = matcher(elem, context, xml) || cachedruns;
                            if (cache[1] === true) {
                                return true;
                            }
                        }
                    }
                }
            }
        };
    }

    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];
    }

    function condense(unmatched, map, filter, context, xml) {
        var elem,
        newUnmatched = [],
            i = 0,
            len = unmatched.length,
            mapped = map != null;

        for (; i < len; i++) {
            if ((elem = unmatched[i])) {
                if (!filter || filter(elem, context, xml)) {
                    newUnmatched.push(elem);
                    if (mapped) {
                        map.push(i);
                    }
                }
            }
        }

        return newUnmatched;
    }

    function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) {
        if (postFilter && !postFilter[expando]) {
            postFilter = setMatcher(postFilter);
        }
        if (postFinder && !postFinder[expando]) {
            postFinder = setMatcher(postFinder, postSelector);
        }
        return markFunction(function(seed, results, context, xml) {
            var temp, i, elem,
            preMap = [],
                postMap = [],
                preexisting = results.length,
                // Get initial elements from seed or context
                elems = seed || multipleContexts(selector || "*", context.nodeType ? [context] : context, []),
                // Prefilter to get matcher input, preserving a map for seed-results synchronization
                matcherIn = preFilter && (seed || !selector) ? condense(elems, preMap, preFilter, context, xml) : elems,
                matcherOut = matcher ?
                // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
                postFinder || (seed ? preFilter : preexisting || postFilter) ?
                // ...intermediate processing is necessary
                [] :
                // ...otherwise use results directly
                results : matcherIn;

            // Find primary matches
            if (matcher) {
                matcher(matcherIn, matcherOut, context, xml);
            }

            // Apply postFilter
            if (postFilter) {
                temp = condense(matcherOut, postMap);
                postFilter(temp, [], context, xml);

                // Un-match failing elements by moving them back to matcherIn
                i = temp.length;
                while (i--) {
                    if ((elem = temp[i])) {
                        matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem);
                    }
                }
            }

            if (seed) {
                if (postFinder || preFilter) {
                    if (postFinder) {
                        // Get the final matcherOut by condensing this intermediate into postFinder contexts
                        temp = [];
                        i = matcherOut.length;
                        while (i--) {
                            if ((elem = matcherOut[i])) {
                                // Restore matcherIn since elem is not yet a final match
                                temp.push((matcherIn[i] = elem));
                            }
                        }
                        postFinder(null, (matcherOut = []), temp, xml);
                    }

                    // Move matched elements from seed to results to keep them synchronized
                    i = matcherOut.length;
                    while (i--) {
                        if ((elem = matcherOut[i]) && (temp = postFinder ? indexOf.call(seed, elem) : preMap[i]) > -1) {

                            seed[temp] = !(results[temp] = elem);
                        }
                    }
                }

                // Add elements to results, through postFinder if defined
            } else {
                matcherOut = condense(
                matcherOut === results ? matcherOut.splice(preexisting, matcherOut.length) : matcherOut);
                if (postFinder) {
                    postFinder(null, results, matcherOut, xml);
                } else {
                    push.apply(results, matcherOut);
                }
            }
        });
    }

    function matcherFromTokens(tokens) {
       //生成用於匹配單個選擇器組的函數
    }

    function matcherFromGroupMatchers(elementMatchers, setMatchers) {
      //生成用於匹配單個選擇器群組的函數
    }

    compile = Sizzle.compile = function(selector, group /* Internal Use Only */ ) {
        var i,
        setMatchers = [],
            elementMatchers = [],
            cached = compilerCache[selector + " "];

        if (!cached) {
            // Generate a function of recursive functions that can be used to check each element
            if (!group) {
                group = tokenize(selector);
            }
            i = group.length;
            while (i--) {
            //比如div:not(.aaa)跑到這里,group只剩下
            //[[{value:":not(.aaa)",type:"PSEUDO",matches:["not","aaa"]}]
                cached = matcherFromTokens(group[i]);
                if (cached[expando]) {
                    setMatchers.push(cached);
                } else {
                    elementMatchers.push(cached);
                }
            }

            // Cache the compiled function
            cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers));
        }
        return cached;
    };

    function multipleContexts(selector, contexts, results) {
        var i = 0,
            len = contexts.length;
        for (; i < len; i++) {
            Sizzle(selector, contexts[i], results);
        }
        return results;
    }

    function select(selector, context, results, seed) {
        var i, tokens, token, type, find,
        match = tokenize(selector);

        if (!seed) {
            // Try to minimize operations if there is only one group
            if (match.length === 1) {//如果只有一個選擇器群組

                tokens = match[0] = match[0].slice(0);
                //如果里面包含ID選擇器, 比如#aaa > div
                if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) {
                    context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0];
                    if (!context) {//如果最左邊的那個祖先都不存在,那么就不用找下去了
                        return results;
                    }
                   //將最初的選擇符去掉ID選擇器 --->  " > div"
                    selector = selector.slice(tokens.shift().value.length);
                }
                // Fetch a seed set for right-to-left matching
                i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
                while (i--) {
                    token = tokens[i];

                    // Abort if we hit a combinator
                    if (Expr.relative[(type = token.type)]) {
                        break;
                    }
                     //find查找器有ID,TAG,NAME,CLASS,都是對應原生API,比如div:not(.aaa),div就優先被處理了
                    if ((find = Expr.find[type])) {
                        //如果tokens[0].type為關系選擇器,則往上找一級,用其父節點作上下文
                        if ((seed = find(
                        token.matches[0].replace(runescape, funescape),
                        rsibling.test(tokens[0].type) && context.parentNode || context))) {
                            //然后去掉用過的選擇器,比如上例中的div
                            tokens.splice(i, 1);
                            selector = seed.length && toSelector(tokens);
                            if (!selector) {//如果選擇符為空白,那么將種子集合並到結構集中
                                push.apply(results, seed);
                                return results;
                            }

                            break;
                        }
                    }
                }
            }
        }

        // Compile and execute a filtering function
        // Provide `match` to avoid retokenization if we modified the selector above
        compile(selector, match)(
        seed,
        context, !documentIsHTML,
        results,
        rsibling.test(selector));
        return results;
    }

    // Deprecated
    Expr.pseudos["nth"] = Expr.pseudos["eq"];

    // Easy API for creating new setFilters

    function setFilters() {}
    setFilters.prototype = Expr.filters = Expr.pseudos;
    Expr.setFilters = new setFilters();


    //判定穩定性
    //http://www.iteye.com/topic/714688
    // Array.prototype.sort在不同瀏覽器中表現可能不一致。 
    //var arr =[{id:1, value:'a'},{id:1, value:'b'},{id:1, value:'c'}]; 
    //var arr = arr.sort(function(a,b){return b.id-a.id}); 
    //for(var n=0,m=arr.length;n<m;n++){ 
    //  alert(arr[n].value); 
    //} 
    //以上代碼在Chrome執行,將得到c,b,a。 
    //而在其他瀏覽器(IE6/7/8,FF3,Opera10,Safari5)中,得到a,b,c。 
    //不強制參與排序的元素保持原來的順序(可以不穩定)。實際上Chrome對比較后相等的元素進行了交換操作,而其他的JS引擎沒有這么做。 
    support.sortStable = expando.split("").sort(sortOrder).join("") === expando;

    // Initialize with the default document
    setDocument();

    // Always assume the presence of duplicates if sort doesn't
    // pass them to our comparison function (as in Google Chrome).
    [0, 0].sort(sortOrder);
    support.detectDuplicates = hasDuplicate;

    // EXPOSE
    if (typeof define === "function" && define.amd) {
        define(function() {
            return Sizzle;
        });
    } else {
        window.Sizzle = Sizzle;
    }
    // EXPOSE

})(window);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM