对jQuery的Sizzle各方法做了深入分析(同时也参考了一些网上资料)后,将结果分享给大家。我将采用连载的方式,对Sizzle使用的一些方法详细解释一下,每篇文章介绍一个方法。
若需要转载,请写明出处,多谢。
/*
* Sizzle方法是Sizzle选择器包的主要入口,jQuery的find方法就是调用该方法获取匹配的节点
* 该方法主要完成下列任务:
* 1、对于单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果
* 2、对于支持querySelectorAll方法的浏览器,通过执行querySelectorAll方法获取并返回匹配的DOM元素
* 3、除上之外则调用select方法获取并返回匹配的DOM元素
*
*
* @param selector 选择器字符串
* @param context 执行匹配的最初的上下文(即DOM元素集合)。若context没有赋值,则取document。
* @param results 已匹配出的部分最终结果。若results没有赋值,则赋予空数组。
* @param seed 初始集合
*/
function Sizzle(selector, context, results, seed) {
var match, elem, m, nodeType,
// QSA vars
i, groups, old, nid, newContext, newSelector;
/*
* preferredDoc = window.document
*
* setDocument方法完成一些初始化工作
*/
if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
setDocument(context);
}
context = context || document;
results = results || [];
/*
* 若selector不是有效地字符串类型数据,则直接返回results
*/
if (!selector || typeof selector !== "string") {
return results;
}
/*
* 若context既不是document(nodeType=9),也不是element(nodeType=1),那么就返回空集合
*/
if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {
return [];
}
// 若当前过滤的是HTML文档,且没有设定seed,则执行if内的语句体
if (documentIsHTML && !seed) {
/*
* 若选择器是单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果
*
* rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/
* 上述正则表达式括号内三段依次分别用来判断是否是ID、TAG、CLASS类型的单一选择器
* 上述正则表达式在最外层圆括号内有三个子表达式(即三个圆括号括起来的部分),
* 分别代表ID、Tag、Class选择器的值,在下面代码中,分别体现在match[1]、match[2]、match[3]
*/
if ((match = rquickExpr.exec(selector))) {
// Speed-up: Sizzle("#ID")
// 处理ID类型选择器,如:#ID
if ((m = match[1])) {
// 若当前上下文是一个document,则执行if内语句体
if (nodeType === 9) {
elem = context.getElementById(m);
// Check parentNode to catch when Blackberry 4.6
// returns
// nodes that are no longer in the document #6963
if (elem && elem.parentNode) {
// Handle the case where IE, Opera, and Webkit
// return items
// by name instead of ID
/*
* 一些老版本的浏览器会把name当作ID来处理,
* 返回不正确的结果,所以需要再一次对比返回节点的ID属性
*/
if (elem.id === m) {
results.push(elem);
return results;
}
} else {
return results;
}
} else {
// Context is not a document
/*
* contains(context, elem)用来确认获取的elem是否是当前context对象的子对象
*/
if (context.ownerDocument
&& (elem = context.ownerDocument.getElementById(m))
&& contains(context, elem) && elem.id === m) {
results.push(elem);
return results;
}
}
// Speed-up: Sizzle("TAG")
// 处理Tag类型选择器,如:SPAN
} else if (match[2]) {
push.apply(results, context.getElementsByTagName(selector));
return results;
// Speed-up: Sizzle(".CLASS")
/*
* 处理class类型选择器,如:.class
* 下面条件判断分别是:
* m = match[3]:有效的class类型选择器
* support.getElementsByClassName 该选择器的div支持getElementsByClassName
* context.getElementsByClassName 当前上下文节点有getElementsByClassName方法
*
*/
} else if ((m = match[3]) && support.getElementsByClassName
&& context.getElementsByClassName) {
push.apply(results, context.getElementsByClassName(m));
return results;
}
}
// QSA path
/*
* 若浏览器支持querySelectorAll方法且选择器符合querySelectorAll调用标准,则执行if内语句体
* 在这里的检查仅仅是简单匹配
* 第一次调用Sizzle时,rbuggyQSA为空
*
* if语句体内对当前context对象的id的赋值与恢复,是用来修正querySelectorAll的一个BUG
* 该BUG会在某些情况下把当前节点(context)也作为结果返回回来。
* 具体方法是,在现有的选择器前加上一个属性选择器:[id=XXX],
* XXX 为context的id,若context本身没有设置id,则给个默认值expando。
*/
if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) {
nid = old = expando;
newContext = context;
// 若context是document,则newSelector取自selector,否则为false
newSelector = nodeType === 9 && selector;
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the
// root
// and working up from there (Thanks to Andrew Dupont for
// the technique)
// IE 8 doesn't work on object elements
if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") {
groups = tokenize(selector);
if ((old = context.getAttribute("id"))) {
/*
* rescape = /'|\\/g,
* 这里将old中的单引号、竖杠、反斜杠前加一个反斜杠
* old.replace(rescape, "\\$&")代码中的$&代表匹配项
*/
nid = old.replace(rescape, "\\$&");
} else {
context.setAttribute("id", nid);
}
nid = "[id='" + nid + "'] ";
// 重新组合新的选择器
i = groups.length;
while (i--) {
groups[i] = nid + toSelector(groups[i]);
}
/*
* rsibling = new RegExp(whitespace + "*[+~]")
* rsibling用于判定选择器是否存在兄弟关系符
* 若包含+~符号,则取context的父节点作为当前节点
*/
newContext = rsibling.test(selector) && context.parentNode
|| context;
newSelector = groups.join(",");
}
if (newSelector) {
/*
* 这里之所以需要用try...catch,
* 是因为jquery所支持的一些选择器是querySelectorAll所不支持的,
* 当使用这些选择器时,querySelectorAll会报非法选择器,
* 故需要jquery自身去实现。
*/
try {
// 将querySelectorAll获取的结果并入results,而后返回resulsts
push.apply(results, newContext
.querySelectorAll(newSelector));
return results;
} catch (qsaError) {
} finally {
if (!old) {
context.removeAttribute("id");
}
}
}
}
}
// All others
// 除上述快捷方式和调用querySelectorAll方式直接获取结果外,其余都需调用select来获取结果
/*
* rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)"
* + whitespace + "+$", "g"),
* whitespace = "[\\x20\\t\\r\\n\\f]";
* 上述rtrim正则表达式的作用是去掉selector两边的空白,空白字符由whitespace变量定义
* rtrim的效果与new RegExp("^" + whitespace + "+|" + whitespace + "+$", "g")相似
*/
return select(selector.replace(rtrim, "$1"), context, results, seed);
}
各位朋友,若觉得写得不错,帮我顶一下,给点动力,多谢!
jQuery选择器代码详解(四)——Expr.preFilter
jQuery选择器代码详解(五)——实例说明tokenize的解析过程
jQuery选择器代码详解(六)——Sizzle选择器匹配逻辑分析