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);
}
04-30 22:06