Math.pow(1.1, Day)

Sizzle浅析-3

Sizzle函数本身主要充当分发器的角色,根据不同的表达式判断条件分成两个主干来处理(为方便说明,我在代码中分为A、B主干,见代码注释):

Sizzle考虑到向后兼容,会通过特性判断来调用浏览器的原生API(如querySelectorAll,getElementsByClassName),为了便于测试,先把调用浏览器API的地方注释掉。

//line 1022左右:
if (document.querySelectorAll)
//修改为 
if (document.querySelectorAll && 0)

//line 1118左右:
(function () {
    var div = document.createElement("div");
    div.innerHTML = "<div class='test e'></div><div class='test'></div>";
    ...
//修改为:
(function () {
    return false ;
    var div = document.createElement("div");
    div.innerHTML = "<div class='test e'></div><div class='test'></div>";
    ...

###A主干-正向查询

如果表达式数组(parts)长度大于1且选择器表达式中包含位置选择器(通过Expr.pos正则匹配判断),则符合从左往右查询的原则,进入A主干。比如:[div, a:first]

在A主干中,再次分为两个分支,记作A.a和A.b。

如果parts长度为2,且第一个元素是关系选择器(“+”, “>”, “~”),则进入A.a分支,直接调用posProcess处理。比如:[+, a:first]

如果不符合A.a的条件则进入A.b分支,根据parts元素来决定上下文。如果parts第一个元素就是是关系选择器,则将当前上下文当作候选集,否则,抽取parts第一个块表达式在当前上下文中 调用Sizzle 获取候选集(从左往右)。接下来对parts里面的元素逐个调用posProcess处理。至此A主干结束。

//A主干处理流程
if (parts.length > 1 && origPOS.exec(selector)) {
    //A.a 单独的一个关系选择器是没办法进行查找的,所以将其和数组中的下一个选择器拼接起来进行查找
    if (parts.length === 2 && Expr.relative[parts[0]]) {
        set = posProcess(parts[0] + parts[1], context);

    //A.b
    } else {
        set = Expr.relative[parts[0]] ? [context] : Sizzle(parts.shift(), context);
        while (parts.length) {
            selector = parts.shift();
            //此处的逻辑和A.a一致
            if (Expr.relative[selector]) {
                selector += parts.shift();
            }
            set = posProcess(selector, set);
        }
    }
}

####位置处理-posProcess

上面的过程中提到了调用posProcess处理带有位置选择器的表达式,这里先看看posProcess是如何工作的。在posProcess中,将位置选择器剔除并保存到later变量中,然后调用Sizzle在对应的 上下文中循环查询剩余的表达式,最后用位置选择器later过滤查询到的结果。源码分析如下:

//posProcess处理逻辑
var posProcess = function (selector, context) {
    var match, tmpSet = [],
        later = "",
        root = context.nodeType ? [context] : context;

    // 将位置选择器滞后处理,由于:not也需要滞后处理,故这里使用了伪类正则(PSEUDO)
    while ((match = Expr.match.PSEUDO.exec(selector))) {
        later += match[0];
        selector = selector.replace(Expr.match.PSEUDO, "");
    }

    selector = Expr.relative[selector] ? selector + "*" : selector;

    for (var i = 0, l = root.length; i < l; i++) {
        Sizzle(selector, root[i], tmpSet);
    }

    return Sizzle.filter(later, tmpSet);
};

###B主干-逆向过滤

如果选择器表达式中没有位置选择器,则采用逆向过滤(从右往走)的策略,进入B主干。

在B主干中会对ID选择器优先处理,如果有表达式中ID选择器,先根据ID查询获取候选集,如果还有残留表达式(比如div.clsName中的clsName),则调用filter继续过滤,并重置context。代码如下:

if (!seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1])) {
    ret = Sizzle.find(parts.shift(), context, contextXML);
    // 根据查询结果中的残留表达式来重置上下文
    context = ret.expr ? Sizzle.filter(ret.expr, ret.set)[0] : ret.set[0];
}

接下来获取候选集。如果有seed(种子集),则生成seed的副本作为候选集,并从parts中弹出一个块表达式作为过滤器表达式(从右向左过滤);如果没有seed,则调用Sizzle查询器获取候选集;如果是兄弟选择器,则重置上下文为当前上下文的父级节点。同上,依然会根据是否有残留表达式来选择是否进行过滤。

查找完候选集后,如果选择器数组中还有元素,则把候选集的副本作为映射集。代码如下:

ret = seed ?  {
    expr: parts.pop(),
    //如果有seed,则使用seed的副本(避免影响seed原始值)
    set: makeArray(seed)
} : Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML);

set = ret.expr ? Sizzle.filter(ret.expr, ret.set) : ret.set;

if (parts.length > 0) {
    // 如果选择器数组中还有元素,则把候选集的副本作为映射集
    checkSet = makeArray(set);
} else {
    // prune [修剪,裁剪]
    // 仅经过简单的find和filter的结果集是不需要再次筛选
    // 在后面的判断中也可以看出,见下面的代码
    prune = false;
}

// 筛选结果集
if (toString.call(checkSet) === "[object Array]") {
    // 如果前面设置了无需筛选,则直接将映射集压入结果集
    if (!prune) {
        results.push.apply(results, checkSet);
    } else if () { 
        // code
    }
}

对选择器数组中的选择器从右向左依次过滤,主要由Expr.relative完成。代码如下:

while (parts.length) {
    cur = parts.pop();
    pop = cur;

    // 如果当前选择器不是关系选择器,则当作祖先选择器处理
    if (!Expr.relative[cur]) {
        cur = "";
    // 
    } else {
        pop = parts.pop();
    }

    if (pop == null) {
        pop = context;
    }

    Expr.relative[cur](checkSet, pop, contextXML);
}

本篇文章主要分析了Sizzle对于各种情况的处理流程。至于核心的查找和过滤方法留在下一篇中分析。