Sizzle浅析-3
- Time: 26 November 2012
- Categories:
- Frontend 27
- Tags:
- Sizzle 7
- jQuery 7
- Javascript 21
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对于各种情况的处理流程。至于核心的查找和过滤方法留在下一篇中分析。