Sizzle源码分析 (一)

Sizzle 源码分析 (一)

2.1 稳定 版本
Sizzle 选择器引擎博大精深,下面开始阅读它的源代码,并从中做出标记 。先从入口开始,之后慢慢切入 。

入口函数 Sizzle ()

源码 194-301 行

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);
}
posted @ 2017-06-09 13:08  小精灵儿Pawn  Views(375)  Comments(1Edit  收藏  举报

如果您觉得此文有帮助,可以打赏点钱给我支付宝。 谢谢 -):