Sizzle源码分析(三) 兼容处理

简介

我们都知道网页可以在不同的浏览器中打开运行,比如IE、firefox、safari、chrome等。不同浏览器内核又有所区别,比如说Edge以前ie的内核是trident,chrome 内核是2013年以前Chromium(webkit)后跟opera联手研究发布了Blink,safari 是webkit/webkit2/,firefox是Gecko。

浏览器冲突由来,以前最早的时候只有一款浏览器Netscape Navigator,没有ie浏览器,也没有chrome等浏览器,那第一个浏览器肯定是老大,自己想实现什么形式的接口,那还不是自己说了算那是绝对标准,比如说Netscape Navigator选中所有的元素接口是document.layers选中页面中的所有元素。经过一段时间微软看到网景公司的浏览器用户非常多在市场占有率非常。微软眼红了,推出了自己的第一款浏览器Internet Explorer(IE)这款浏览器比网景遵循World Wide Web Consortium (W3C)提出的互联网标准。IE想占有市场,就把window系统上绑定了IE浏览器。IE中选中所有元素用document.all。

IE打败了网景,紧接着网景1998年开源了Netscape navigatior的源码,并重新命名为Mozilla, 后来04年发布了Firefox浏览器大战又拉开了序幕。陆续加入大战的有opera、safari、chrome等。IE打败网景自己就变成了老大,自己就是绝对标准了,实现接口的时候比如说绑定事件ie使用 attachEvent而其他浏览器都是addEventListener。所以以前的程序员写代码的时候首先得做兼容处理,如果你支持attachEvent就使用这个,如果支持addEventListener。当然兼容问题不只这一个。

变量声明

var document// 根文档,
arr = [], // 
pop = arr.pop,
pushNative = arr.push,
push = arr.push,
slice = arr.slice,
preferredDoc = window.document, // 缓存当前的文档对象
matchExpr= {},
dirruns = 0, //
done = 0,
复制代码

try catch 初始化

这里对document做了一个存储,对arr.apply做了一个兼容处理,主要是用来兼容android < 4.0的


try {
        push.apply(
                (arr = slice.call(preferredDoc.childNodes)),
                preferredDoc.childNodes
        );
        这里就是arr.push.apply(arr = slice.call(prefereredDoc.childNodes), ...)
        这里把文档存了两遍
        // Support: Android<4.0
        // Detect silently failing push.apply
        // eslint-disable-next-line no-unused-expressions
        // nodeType 一般是10DocumentType(向为文档定义的实体提供接口), 如果不是10的话会到下面重新arr.apply
        arr[preferredDoc.childNodes.length].nodeType;
} catch (e) {
    push = {
        // 上面没有拿到nodeType报异常,判断length是否有
        apply: arr.length ?

                // Leverage slice if possible
                function(target, els) {
                        pushNative.apply(target, slice.call(els));
                } :

                // Support: IE<9
                // Otherwise append directly
                function(target, els) {
                        var j = target.length,
                                i = 0;

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

复制代码

support

主要测试是否支持公共兼容性。支持support方法主要是在 设置文档的时候做一个兼容行处理

// Expose support vars for convenience
support = Sizzle.support = {};
复制代码

unloadHandler

用于处理ie情况下,删除包装函数导致没有权限访问iframe

unloadHandler = function() {
        setDocument();
}
复制代码

setDocument

我做一个简单的拆分,我本来是想,代码很简单一看就懂,但是还是做一个拆分,这块主要做了一些方法的初始化,和在不同文档下的兼容性处理,

// 这里主要是找对document对象,因为当前元素可能是iframe下的一个元素,若是一个iframe的一个元素的话就要找回当前文档,及事件的兼容处理
setDocument = Sizzle.setDocument = function(node) {
    var hasCompare, subWindow,
    // 设置文档节点 如果node存在返回 nide.ownerDocument 不存在就返回node 否则返回prefererdDoc
            doc = node ? node.ownerDocument || node : preferredDoc;

    // Return early if doc is invalid or already selected
    // Support: IE 11+, Edge 17 - 18+
    // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
    // two documents; shallow comparisons work.
    // eslint-disable-next-line eqeqeq
    // document.nodeType 9 代表整个文档
    // 文档不存在的话直接返回根文档
    if (doc == document || doc.nodeType !== 9 || !doc.documentElement) {
            return document;
    }
    // 如果存在,直接更新document指向
    // Update global variables
    document = doc;
    docElem = document.documentElement;
    // 判断是否是XML
    documentIsHTML = !isXML(document);

    // Support: IE 9 - 11+, Edge 12 - 18+
    // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
    // Support: IE 11+, Edge 17 - 18+
    // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
    // two documents; shallow comparisons work.
    // eslint-disable-next-line eqeqeq
    // 如果缓存文档不是当前的文档
    // 拿会当前文档
    if (preferredDoc != document &&
            (subWindow = document.defaultView) && subWindow.top !== subWindow) {

            // Support: IE 11, Edge
            if (subWindow.addEventListener) {
                    subWindow.addEventListener("unload", unloadHandler, false);

                    // Support: IE 9 - 10 only
            } else if (subWindow.attachEvent) {
                    subWindow.attachEvent("onunload", unloadHandler);
            }
    }
}

复制代码

supprt 常用选择器api支持情况兼容

// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
// Safari 4 - 5 only, Opera <=11.6 - 12.x only
// IE/Edge & older browsers don't support the :scope pseudo-class.
// Support: Safari 6.0 only
// Safari 6.0 supports :scope but it's an alias of :root there.
// 
support.scope = assert(function(el) {
//看这个元素是否支持:scope选择器
//很容易看懂
    docElem.appendChild(el).appendChild(document.createElement("div"));
    return typeof el.querySelectorAll !== "undefined" &&
            !el.querySelectorAll(":scope fieldset div").length;
});
// Support: IE<8
// Verify that getAttribute really returns attributes and not properties
// (excepting IE8 booleans)
//是否支持getAttribute属性,主要是是小于ie8 的测试
support.attributes = assert(function(el) {
        el.className = "i";
        return !el.getAttribute("className");
});
/* getElement(s)By*
---------------------------------------------------------------------- */

// Check if getElementsByTagName("*") returns only elements
support.getElementsByTagName = assert(function(el) {
        el.appendChild(document.createComment(""));
        return !el.getElementsByTagName("*").length;
});

// Support: IE<9
// 测试是否支持getElementsByClassName
support.getElementsByClassName = rnative.test(document.getElementsByClassName);


// Support: IE<10
// Check if getElementById returns elements by name
// The broken getElementById methods don't pick up programmatically-set names,
// so use a roundabout getElementsByName test
support.getById = assert(function(el) {
    docElem.appendChild(el).id = expando;
    return !document.getElementsByName || !document.getElementsByName(expando).length;
});

复制代码

Expr.filter 兼容id选择器

主要过Expr.filter、Expr.find 做兼容处理。比如用一个元素的id 是myId 的元素,就使用Expr.find(id, context)

// ID filter and find 
if (support.getById) {
        Expr.filter["ID"] = function(id) {
               // 返回当前的id
                var attrId = id.replace(runescape, funescape);
                // 返回true or false
                return function(elem) {
                        return elem.getAttribute("id") === attrId;
                };
        };
        // 查找元素如果有这个元素就直接返回
        Expr.find["ID"] = function(id, context) {
                if (typeof context.getElementById !== "undefined" && documentIsHTML) {
                        var elem = context.getElementById(id);
                        return elem ? [elem] : [];
                }
        };
} else {
        Expr.filter["ID"] = function(id) {
                var attrId = id.replace(runescape, funescape);
                return function(elem) {
                        var node = typeof elem.getAttributeNode !== "undefined" &&
                        //返回属性
                                elem.getAttributeNode("id");
                        return node && node.value === attrId;
                };
        };
            
        // Support: IE 6 - 7 only
        // getElementById is not reliable as a find shortcut
        这里说一下这个兼容怎么处理的,主要看是否兼容getElementById
        若果不兼容就拿到这个属性id的tagname ,拿到所有tag然后 看看哪个id相同,然后就匹配返回哪个思路就是这样,若果没有就返回[]
        Expr.find["ID"] = function(id, context) {
                if (typeof context.getElementById !== "undefined" && documentIsHTML) {
                        var node, i, elems,
                                elem = context.getElementById(id);

                        if (elem) {

                                // Verify the id attribute
                                node = elem.getAttributeNode("id");
                                if (node && node.value === id) {
                                        return [elem];
                                }

                                // Fall back on getElementsByName
                                elems = context.getElementsByName(id);
                                i = 0;
                                while ((elem = elems[i++])) {
                                        node = elem.getAttributeNode("id");
                                        if (node && node.value === id) {
                                                return [elem];
                                        }
                                }
                        }

                        return [];
                }
        };
}

复制代码

Expr.filter 兼容tag选择器


// Tag
查看是否兼容getElementsByTagName,兼容执行“?”后函数,兼容走“:”
Expr.find["TAG"] = support.getElementsByTagName ?
        function(tag, context) {
                if (typeof context.getElementsByTagName !== "undefined") {
                        return context.getElementsByTagName(tag);

                        // DocumentFragment nodes don't have gEBTN
                        // Fragment没有getElementsByTagName这个方法
                } else if (support.qsa) {
                        return context.querySelectorAll(tag);
                }
        } :
        // 处理兼容
        // 如果不兼容,选中所有元素,把tag相同的都放入result中返回
        function(tag, context) {
                var elem,
                        tmp = [],
                        i = 0,

                        // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
                        results = context.getElementsByTagName(tag);

                // Filter out possible comments
                if (tag === "*") {
                        while ((elem = results[i++])) {
                                if (elem.nodeType === 1) {
                                        tmp.push(elem);
                                }
                        }

                        return tmp;
                }
                return results;
        };
复制代码

Expr.filter 兼容class选择器

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

querySelectorAll 兼容行测试

这里我请大家细看代码如果和做兼容处理的,我会给下面做一些注释非常明了

// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
rbuggyMatches = [];

// qSa(:focus) reports false when true (Chrome 21)
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
// See https://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
// 如果存在querySelectorAll, support.qsa 为true
if ((support.qsa = rnative.test(document.querySelectorAll))) {

        // Build QSA regex
        // Regex strategy adopted from Diego Perini
        assert(function(el) {
                var input;

                // Select is set to empty string on purpose
                // This is to test IE's treatment of not explicitly
                // setting a boolean content attribute,
                // since its presence should be enough
                // https://bugs.jquery.com/ticket/12359
                //// 主要测试ie <option selected=''></option> selected的属性,给el元素填写测试元素
                docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
                        "<select id='" + expando + "-\r\\' msallowcapture=''>" +
                        "<option selected=''></option></select>";

                // Support: IE8, Opera 11-12.16
                // Nothing should be selected when empty strings follow ^= or $= or *=
                // The test attribute must be unknown in Opera but "safe" for WinRT
                // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
                //看是支持属性是msallowcapture开头是^='' 的属性,若果不支持那就是qsa的bug,记录在rbuggyQSA中,下面也是这样。不好理解的我都会写注释
                if (el.querySelectorAll("[msallowcapture^='']").length) {
                        rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
                }

                // Support: IE8
                // Boolean attributes and "value" are not treated correctly
                if (!el.querySelectorAll("[selected]").length) {
                        rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
                }

                // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
                if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
                        rbuggyQSA.push("~=");
                }

                // Support: IE 11+, Edge 15 - 18+
                // IE 11/Edge don't find elements on a `[name='']` query in some cases.
                // Adding a temporary attribute to the document before the selection works
                // around the issue.
                // Interestingly, IE 10 & older don't seem to have the issue.
                input = document.createElement("input");
                input.setAttribute("name", "");
                el.appendChild(input);
                if (!el.querySelectorAll("[name='']").length) {
                        rbuggyQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" +
                                whitespace + "*(?:''|\"\")");
                }

                // Webkit/Opera - :checked should return selected option elements
                // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
                // IE8 throws error here and will not see later tests
                if (!el.querySelectorAll(":checked").length) {
                        rbuggyQSA.push(":checked");
                }

                // Support: Safari 8+, iOS 8+
                // https://bugs.webkit.org/show_bug.cgi?id=136851
                // In-page `selector#id sibling-combinator selector` fails
                if (!el.querySelectorAll("a#" + expando + "+*").length) {
                        rbuggyQSA.push(".#.+[+~]");
                }

                // Support: Firefox <=3.6 - 5 only
                // Old Firefox doesn't throw on a badly-escaped identifier.
                el.querySelectorAll("\\\f");
                rbuggyQSA.push("[\\r\\n\\f]");
        });

        assert(function(el) {
                el.innerHTML = "<a href='' disabled='disabled'></a>" +
                        "<select disabled='disabled'><option/></select>";

                // Support: Windows 8 Native Apps
                // The type and name attributes are restricted during .innerHTML assignment
                var input = document.createElement("input");
                input.setAttribute("type", "hidden");
                el.appendChild(input).setAttribute("name", "D");

                // Support: IE8
                // Enforce case-sensitivity of name attribute
                if (el.querySelectorAll("[name=d]").length) {
                        rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
                }

                // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
                // IE8 throws error here and will not see later tests
                if (el.querySelectorAll(":enabled").length !== 2) {
                        rbuggyQSA.push(":enabled", ":disabled");
                }

                // Support: IE9-11+
                // IE's :disabled selector does not pick up the children of disabled fieldsets
                docElem.appendChild(el).disabled = true;
                if (el.querySelectorAll(":disabled").length !== 2) {
                        rbuggyQSA.push(":enabled", ":disabled");
                }

                // Support: Opera 10 - 11 only
                // Opera 10-11 does not throw on post-comma invalid pseudos
                el.querySelectorAll("*,:x");
                rbuggyQSA.push(",.*:");
        });
}

复制代码

matchesSelector

matcheesSelector 对应Element.matches() , 这里做了一个兼容性的处理,若果这个元素包含这个选择器就返回true


if ((support.matchesSelector = rnative.test((matches = docElem.matches ||
                docElem.webkitMatchesSelector ||
                docElem.mozMatchesSelector ||
                docElem.oMatchesSelector ||
                docElem.msMatchesSelector)))) {

        assert(function(el) {

                // Check to see if it's possible to do matchesSelector
                // on a disconnected node (IE 9)
                // 元素断开节点转义到el上
                support.disconnectedMatch = matches.call(el, "*");
                // This should fail with an exception
                // Gecko does not error, returns false instead
                matches.call(el, "[s!='']:x");
                rbuggyMatches.push("!=", pseudos);
        });
      // rbuggyQSA 转成正则,放在后来处理这类型不兼容的选择器
      rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|"));
      // rbuggyMatches 转成正则,放在后来处理这类型不兼容的选择器
      rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|"));
}

复制代码

hasCompare

查看看元素在文档中的位置, compareDocumentPosition是否实现
compareDocumentPosition是否实现可以比较当前节点与任意文档中的另一个节点的位置关系
1 表示不在一个文档
2 当前节点之前
4 当前节点之后
8 包含当前节点
16 被当前节点包含
32 待定

hasCompare = rnative.test(docElem.compareDocumentPosition);

复制代码

contains

node.contains 来表示传入的节点是否为该节点的后代节点,返回true or false

// Element contains another
// Purposefully self-exclusive
// As in, an element does not contain itself
// 兼容是否实现contains 或者hasCompare
contains = hasCompare || rnative.test(docElem.contains) ?
function(a, b) {
        // 若果是跟节点直返回跟节点,拿到a的根节点 否则返回a节点
        var adown = a.nodeType === 9 ? a.documentElement : a,
        则查看b,拿到b元素的父节点
                bup = b && b.parentNode;
        // 如果a 节点 === b的父节点 或者 (b的父节点是元素节点 就看adown是否有contains)如果有就返回adown.contains(bup) 否则就用compareDocumentPosition 来比较。
        return a === bup || !!(bup && bup.nodeType === 1 && (
                adown.contains ?
                adown.contains(bup) :
                a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
        ));
} :
// 如果不支持就使用这个方法来判断,a是否包含b 就一直找b的父节点,如果有一样的返回true 否则就false
function(a, b) {
        if (b) {
                while ((b = b.parentNode)) {
                        if (b === a) {
                                return true;
                        }
                }
        }
        return false;
};
复制代码

sortOrder

// Document order sorting
给文档排序

// Document order sorting
sortOrder = hasCompare ?
function(a, b) {

        // Flag for duplicate removal
        // a === b 的话就会hasDuplicate = true 表示重复直接返回0
        if (a === b) {
                hasDuplicate = true;
                return 0;
        }

        // Sort on method existence if only one input has compareDocumentPosition
        // 若果这个方法存在就直接使用 compareDocumentPosition 返回类型1 ,2 , 4, 8 , 16, 32
        var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
        if (compare) {
                return compare;
        }

        // Calculate position if both inputs belong to the same document
        // Support: IE 11+, Edge 17 - 18+
        // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
        // two documents; shallow comparisons work.
        // eslint-disable-next-line eqeqeq
        // 如果a b是同一个文档下, 使用 a.compareDocumentPosition(b) ,否则不是一个文档
        compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
                a.compareDocumentPosition(b) :

                // Otherwise we know they are disconnected
                1;

        // Disconnected nodes
        if (compare & 1 ||
                (!support.sortDetached && b.compareDocumentPosition(a) === compare)) {

                // Choose the first element that is related to our preferred document
                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                // eslint-disable-next-line eqeqeq
                if (a == document || a.ownerDocument == preferredDoc &&
                        contains(preferredDoc, a)) {
                        return -1;
                }

                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                // eslint-disable-next-line eqeqeq
                if (b == document || b.ownerDocument == preferredDoc &&
                        contains(preferredDoc, b)) {
                        return 1;
                }

                // Maintain original order
                return sortInput ?
                        (indexOf(sortInput, a) - indexOf(sortInput, b)) :
                        0;
        }

        return compare & 4 ? -1 : 1;
} :
function(a, b) {

        // Exit early if the nodes are identical
        if (a === b) {
                hasDuplicate = true;
                return 0;
        }

        var cur,
                i = 0,
                aup = a.parentNode,
                bup = b.parentNode,
                ap = [a],
                bp = [b];

        // Parentless nodes are either documents or disconnected
        // 不存在父节点 就的与当前文档断开连接了
        if (!aup || !bup) {

                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                /* eslint-disable eqeqeq */
                // 如果a 是document 返回 -1
                return a == document ? -1 :
                         否则看b是不是document 是的话返回1,如果不是
                        b == document ? 1 :
                        /* eslint-enable eqeqeq */
                        看aup 存在就返回-1 
                        aup ? -1 :
                        否则看bup存在吗,存在返回1,
                        bup ? 1 :
                        //不存在查看sortInput 是否存在?存在就开始在input里面找位置,找到后返回
                        sortInput ?
                        (indexOf(sortInput, a) - indexOf(sortInput, b)) :
                        //否则返回0
                        0;

                // If the nodes are siblings, we can do a quick check
                // 如果a,b父节点相同,检测是不是兄弟节点
        } else if (aup === bup) {
           // 返回文档中两节点的顺序
                return siblingCheck(a, b);
        }

        // Otherwise we need full lists of their ancestors for comparison
        cur = a;
        // 如果a ,有父元素,一直查找父元素放入ap
        while ((cur = cur.parentNode)) {
                ap.unshift(cur);
        }
        cur = b;
        while ((cur = cur.parentNode)) {
                bp.unshift(cur);
        }
        // 然后找ap 与bp的差异
        // Walk down the tree looking for a discrepancy
        while (ap[i] === bp[i]) {
                i++;
        }

        return i ?

                // Do a sibling check if the nodes have a common ancestor
               // 返回文档中两节点的顺序, 如果a在b之前返回小于-1 如果a在b之后返回大于0
                siblingCheck(ap[i], bp[i]) :

                // Otherwise nodes in our document sort first
                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                /* eslint-disable eqeqeq */
                // 看ap[i] 是否等于 window.documnet
                ap[i] == preferredDoc ? -1 :
                bp[i] == preferredDoc ? 1 :
                /* eslint-enable eqeqeq */
                0;
};

复制代码

如果要转载请写明来源谢谢大家

Sizzle源码分析(一) 基本概念

Sizzle源码分析(二) 工具方法

Sizzle源码分析(三) 兼容处理

Sizzle源码分析(四) sizzle静态方法分解

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享