• 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);
    }
    
  • 相关阅读:
    Tarjan算法与割点割边
    kmp匹配
    最小表示法
    字典树
    哈希
    网络流入门浅谈
    关于两道搜索的题目
    2020 4.2校内测题解
    LIS最长上升子序列讲解&&洛谷P1439 【模板】最长公共子序列 题解
    浅谈拉格朗日插值公式
  • 原文地址:https://www.cnblogs.com/likeFlyingFish/p/6971553.html
Copyright © 2020-2023  润新知