Math.pow(1.1, Day)

Sizzle浅析-5

上篇文章分析了Sizzle的查找功能。较之于查找,过滤就显得颇为复杂了。它分为元素之间的过滤和元素自身过滤(filter)。元素之间的过滤分为:相邻兄弟过滤,父级过滤,祖先过滤,通用兄弟过滤,这四种过滤方式定义在Expr.relative对象中。自身过滤由Sizzle.filter方法完成。

###元素间过滤——Expr.relative

####相邻兄弟过滤——”+” 要过滤相邻兄弟节点,故将映射集中的每个元素的上一个相邻兄弟节点和传入的参数part做比较。将对应的比较结果存入映射集。由于part的类型不确定,所以源码中针对每个类型做了不同的处理,具体看下面代码中的注释。

var Expr.relative['+'] = function (checkSet, part) {
    var isPartStr = typeof part === "string",
        isTag = isPartStr && !/\W/.test(part),
        isPartStrNotTag = isPartStr && !isTag; // 块表达式

    if (isTag) {
        part = part.toLowerCase();
    }

    for (var i = 0, l = checkSet.length, elem; i < l; i++) {
        if ((elem = checkSet[i])) {
            // 查找上一个相邻的Element类型节点(部分浏览会把Element节点直接的换行符也当作一个Text节点)
            while ((elem = elem.previousSibling) && elem.nodeType !== 1) {}

            //注意逻辑运算符的优先级 "===" > "&&" > "||" > "?"
            // 如果part是tag或块表达式,返回elem||false
            // 否则(part是Element类型),返回elem===part
            checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part;
        }
    }

    // 如果是块表达式,则调用自身过滤
    if (isPartStrNotTag) {
        Sizzle.filter(part, checkSet, true);
    }
};

####父级元素过滤——”>” 相邻兄弟过滤器类似,取映射集中每个元素的父级节点和part比较,根据part类型的差异,将对应的比较结果存入映射集。

var Expr.relative['>'] = function (checkSet, part) {
    var elem, isPartStr = typeof part === "string",
        i = 0, l = checkSet.length;

    // 如果是tag类型
    if (isPartStr && !/\W/.test(part)) {
        part = part.toLowerCase();
        for (; i < l; i++) {
            elem = checkSet[i];
            if (elem) {
                var parent = elem.parentNode;
                checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
            }
        }
    // part为块表达式或Element节点
    } else {
        for (; i < l; i++) {
            elem = checkSet[i];
            if (elem) {
                checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part;
            }
        }
        if (isPartStr) {
            Sizzle.filter(part, checkSet, true);
        }
    }
};

####祖先元素过滤——”” 和 通用兄弟过滤——”~” 祖先元素过滤和通用兄弟过滤都涉及递归查询操作(祖先元素过滤需要递归查找父级元素,通用兄弟过滤需要递归查找上一个相邻节点),所以我合在一起说明。

var Expr.relative[''] = function (checkSet, part, isXML) {
    var nodeCheck, doneName = done++,
        checkFn = dirCheck;

    if (typeof part === "string" && !/\W/.test(part)) {
        part = part.toLowerCase();
        nodeCheck = part;
        checkFn = dirNodeCheck;
    }

    checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
    // 通用兄弟过滤这里传递的的参数是 previousSibling
    // checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
};

在这里只做简单的入口判断,核心过滤工作是交给dirNodeCheck或dirCheck来实现(如果part是tag类型则交给dirNodeCheck处理,否则交给dirCheck处理)。

递归查询候选集中每个元素的相对元素(父级元素或上一个兄弟节点),直到发现该相对元素的nodeName恰好等于传入的tagName(cur)。这里使用了缓层技术减少循环判断次数,极大提升了过滤效率。

function dirNodeCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) {
    for (var i = 0, l = checkSet.length; i < l; i++) {
        var elem = checkSet[i];
        if (elem) {
            var match = false;
            elem = elem[dir];
            while (elem) {
                if (elem.sizcache === doneName) {
                    match = checkSet[elem.sizset];
                    break;
                }
                if (elem.nodeType === 1 && !isXML) {
                    elem.sizcache = doneName;
                    elem.sizset = i;
                }
                if (elem.nodeName.toLowerCase() === cur) {
                    match = elem;
                    break;
                }
                elem = elem[dir];
            }
            checkSet[i] = match;
        }
    }
}

处理完元素之间的过滤,再看看元素自身的过滤。

###元素自身过滤——Expr.filter

如同Sizzle.find,Sizzle.filter也只是过滤方法的一个入口。首先根据正则来判断选择器的类型,然后调用对应的预过滤函数(Expr.preFilter)和过滤函数(Expr.filter)处理。

// 特殊参数说明
// inplace 是否在原结果中修改
// not 是否取补集
Sizzle.filter = function (expr, set, inplace, not) {
    var match, anyFound, old = expr,
        result = [],
        curLoop = set,
        isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);

    while (expr && set.length) {
        for (var type in Expr.filter) {
            if ((match = Expr.leftMatch[type].exec(expr)) != null && match[2]) {
                var found, item, filter = Expr.filter[type], left = match[1];

                anyFound = false;
                match.splice(1, 1);

                // leftMatch的意义在于判断表达式前面的字符是否为转义符
                if (left.substr(left.length - 1) === "\\") {
                    continue;
                }
                // 这个条件看起来不可能成立 []!==[]
                if (curLoop === result) {
                    result = [];
                }

                // 预过滤
                if (Expr.preFilter[type]) {
                    match = Expr.preFilter[type](match, curLoop, inplace, result, not, isXMLFilter);
                    if (!match) {
                        anyFound = found = true;
                    } else if (match === true) {
                        continue;
                    }
                }

                if (match) {
                    for (var i = 0; (item = curLoop[i]) != null; i++) {
                        // 这里的判读多余
                        if (item) {
                            // 过滤
                            found = filter(item, match, i, curLoop);
                            // 使用异或运算获取补集,比普通的逻辑书写优雅多了 Nice
                            var pass = not ^ !! found;

                            if (inplace && found != null) {
                                if (pass) {
                                    anyFound = true;
                                } else {
                                    curLoop[i] = false;
                                }
                            } else if (pass) {
                                result.push(item);
                                anyFound = true;
                            }
                        }
                    }
                }

                if (found !== undefined) {
                    if (!inplace) {
                        curLoop = result;
                    }
                    expr = expr.replace(Expr.match[type], "");
                    if (!anyFound) {
                        return [];
                    }
                    break;
                }
            }
        }

        // Improper expression
        if (expr === old) {
            if (anyFound == null) {
                Sizzle.error(expr);

            } else {
                break;
            }
        }
        old = expr;
    }
    return curLoop;
};

####预过滤 预过滤主要是用于修正表达式的参数以便于接下来的过滤操作。但有个特例,ClASS选择器的过滤直接在预过滤过程中完成,不需要再执行后续的过滤。这里截取部分源码说明。

//预过滤
var Expr.preFilter = {
    CLASS: function (match, curLoop, inplace, result, not, isXML) {
        match = " " + match[1].replace(/\\/g, "") + " ";
        if (isXML) {
            return match;
        }
        for (var i = 0, elem; (elem = curLoop[i]) != null; i++) {
            if (elem) {
                if (not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0)) {
                    if (!inplace) {
                        result.push(elem);
                    }
                } else if (inplace) {
                    curLoop[i] = false;
                }
            }
        }
        // 由于在上面的步骤中已经执行了过滤操作,故这里一律返回false,不需要执行后面的后面的过滤操作。
        return false;
    },

    // 部分其他选择器只是修正表达式参数
    ID: function (match) {
        return match[1].replace(/\\/g, "");
    },
    //...
}

####过滤 将经过修正后的表达式交给Expr.filter执行最后一层过滤。这个过程中是根据Element节点自身的属性(如id,class, attribute, pseudo等)来过滤。所以涉及情况非常繁多,所以函数间的调用也会稍微复杂,比如伪类的过滤(Expr.filter.PSEUDO)调用了Expr.filters来执行过滤,位置选择器的过滤下(Expr.filter.POS)调用了Expr.setFilters来执行过滤。如果感觉有点头大,请参照前面提到的Sizzle函数结构图。

var Expr.filter = {
    ID: function (elem, match) {
        return elem.nodeType === 1 && elem.getAttribute("id") === match;
    },
    ATTR: function (elem, match) {
        // 可结合前面定义的正则看
        // ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
        var name = match[1],
            result = Expr.attrHandle[name] ? Expr.attrHandle[name](elem) : elem[name] != null ? elem[name] : elem.getAttribute(name),
            value = result + "",
            type = match[2],
            check = match[4];

        // 把源码格式化了下,看起来简单多了 t_t
        // 判断Element节点的属性是否和选择器中的属性匹配
        return result == null ? type === "!=" :
            type === "=" ? value === check :
            type === "*=" ? value.indexOf(check) >= 0 :
            type === "~=" ? (" " + value + " ").indexOf(check) >= 0 :
            !check ? value && result !== false :
            type === "!=" ? value !== check :
            type === "^=" ? value.indexOf(check) === 0 :
            type === "$=" ? value.substr(value.length - check.length) === check :
            type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" :
            false;
    },
    // ...
}

###终极筛选

经过一系列查询和过滤之后,Sizzle结合映射集在结果集中筛选符合条件的结果。

if (toString.call(checkSet) === "[object Array]") {
    // 这里前面已经解释过了
    if (!prune) {
        results.push.apply(results, checkSet);
    } else if (context && context.nodeType === 1) {
        for (i = 0; checkSet[i] != null; i++) {
            // 你可能会明白了前面为什么经常把布尔值存入到映射集中了,映射集才不是最终结果呢 
            if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) {
                results.push(set[i]);
            }
        }
    } else {
        for (i = 0; checkSet[i] != null; i++) {
            if (checkSet[i] && checkSet[i].nodeType === 1) {
                results.push(set[i]);
            }
        }
    }
} else {
    makeArray(checkSet, results);
}

if (extra) {
    Sizzle(extra, origContext, results, seed);
    Sizzle.uniqueSort(results);
}

就这样,第一个群组选择器搞定了,如果还有其他群组选择器(extra),则递归调用sizzle查询extra,最后对结果进行合并、去重、排序。整体流程分析至此,大致流程图。

sizzle process

若有不当,还请斧正。