前言

我们继续从init()方法中的find()方法往下看,

jQuery.find = Sizzle;
... find: function (selector) {
/** ... */ ret = this.pushStack([]); // 还是调用的递归栈方法 for (i = 0; i < len; i++) {
jQuery.find(selector, self[i], ret); // 寻找selector,也就是进入Sizzle构造函数
} return len > 1 ? jQuery.uniqueSort(ret) : ret;
},




选择器入口:Sizzle() 构造函数

执行流程:

  1. 首先再次判断选择器是否无效,无效则直接return;
  2. 根据有无种子(最后结果是其子集)传入,无种子则顺序直接,有种子则直接进入Select()
  3. 然后利用setDocument验证浏览器对各个属性的支持情况,此函数同时还提供各种匹配函数;
  4. 接着判断选择器类型:
    1. 根据选择器的复杂程度,先区分出ID、TAGS、CLASS三种选择器。其中ID选择器根据上下文的不同分两种情况,其他的直接调用浏览器函数得到结果即可;
    2. 如果选择器很复杂(不是上述三类选择器),则在方法内部调用tokenize()实现复杂的结构解析,再利用querySelectorAll()函数进行查找,如果此方法不兼容选择器则进入Selct()方法。
  5. 相对于前面的方法,Select()承载了重要的作用,其既能够实现复杂结构选择器的定位,也能够实现在种子中匹配相应子集,相应的是其难以理解,因为增加了更多的代码和匹配器
// Sizzle构造函数
function Sizzle(selector, context, results, seed) {
var m, i, elem, nid, match, groups, newSelector,
newContext = context && context.ownerDocument, // 如果没有传入上下文,则节点类型默认为9
nodeType = context ? context.nodeType : 9; results = results || []; // 如果是无效的选择器或者文本,则直接返回
if (typeof selector !== "string" || !selector ||
// 1--元素, 9--文档,11-轻量级文档对象
nodeType !== 1 && nodeType !== 9 && nodeType !== 11) { return results;
} // 尝试在HTML文档中使用快捷方式查找操作(而不是过滤器)
if (!seed) {
// 查看各个浏览器对各种操作的支持,并提供expr.find()校验方式
/********************* ***********************/
setDocument(context);
/********************* ***********************/ context = context || document; if (documentIsHTML) { // 定义在setDocument文件中,表示其不是XML节点 // 如果选择器能够快速被检测出,直接用get**by**
if (nodeType !== 11 && (match = rquickExpr.exec(selector))) { // ID selector
if ((m = match[1])) { // Document context // 也就是 nodeType === 9
if (nodeType === 9) {
if ((elem = context.getElementById(m))) { // 在IE,Opera,webkit中,getElementById可以按名称
// 而不是ID匹配元素,因此需要用属性id的值重新比较
if (elem.id === m) {
results.push(elem);
return results;
}
} else {
return results;
} // Element context // 也就是 nodeType === 1
} else {
if (newContext && (elem = newContext.getElementById(m)) &&
contains(context, elem) &&
elem.id === m) { results.push(elem);
return results;
}
} // Type selector // TAGS 选择器
} else if (match[2]) {
push.apply(results, context.getElementsByTagName(selector));
return results; // Class selector // class 类选择器
} else if ((m = match[3]) && support.getElementsByClassName &&
context.getElementsByClassName) { push.apply(results, context.getElementsByClassName(m));
return results;
}
} // Take advantage of querySelectorAll // 利用CSS选择器查询
if (support.qsa &&
!nonnativeSelectorCache[selector + " "] &&
(!rbuggyQSA || !rbuggyQSA.test(selector)) && // Support: IE 8 only
// Exclude object elements, 排除 对象元素
(nodeType !== 1 || context.nodeName.toLowerCase() !== "object")) { newSelector = selector;
newContext = context; // 上面的话的意思是:querySelectorAll()在计算子结合时会考虑根节点以外的元素,
// 因此给列表中每个选择器添加一个前缀ID选择器,便于QSA识别
if (nodeType === 1 &&
// 正则表达式验证
(rdescend.test(selector) || rcombinators.test(selector))) { // 展开同级选择器的上下文
newContext = rsibling.test(selector) && testContext(context.parentNode) || context; // 如果浏览器支持 :scope并且我们不改变上下文,则我们可以用 :scope替代ID
if (newContext !== context || !support.scope) { // Capture the context ID, setting it first if necessary
if ((nid = context.getAttribute("id"))) { // 如果存在,则替换
// 字符串解码
nid = nid.replace(rcssescape, fcssescape);
} else { // 不能存在直接添加属性id
context.setAttribute("id", (nid = expando));
}
} // 词义分析
/**********************************************/
groups = tokenize(selector);
/**********************************************/
i = groups.lengthlength;
while (i--) {
groups[i] = (nid ? "#" + nid : ":scope") + " " +
toSelector(groups[i]);
}
newSelector = groups.join(",");
} try {
push.apply(results,
// 此方法不能检测复杂的String,如"#aa .bb",只能通过select()
newContext.querySelectorAll(newSelector)
);
return results;
} catch (qsaError) {
nonnativeSelectorCache(selector, true);
} finally {
if (nid === expando) {
context.removeAttribute("id");
}
}
}
}
} // 只有结构复杂的选择器才会执行到这步,多个父元素、伪类、限定符等
return select(selector.replace(rtrim, "$1"), context, results, seed);
}




准备工作:setDocument()函数

因为不同的浏览器支持不同的方法,也不一定支持所有属性。为了能够顺利获得各种属性以供选择器定位,我们首先要获得各种浏览器对对象属性的支持情况,然后据此指定不同的拦截器和过滤器。这也是为了兼容不得不做的牺牲。

同时此函数内部定义了判定包含和排序两个函数,由于此函数只用于选择器定位,因此写在此处。

// setDocument()函数
setDocument = Sizzle.setDocument = function (node) {
var hasCompare, subWindow,
doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected
if (doc == document || doc.nodeType !== 9 || !doc.documentElement) {
return document;
} // 更新全局变量
document = doc;
docElem = document.documentElement;
documentIsHTML = !isXML(document); // eslint-disable-next-line eqeqeq
if (preferredDoc != document &&
(subWindow = document.defaultView) && subWindow.top !== subWindow) { // Support: IE 11, Edge
if (subWindow.addEventListener) {
subWindow.addEventListener("unload", unloadHandler, false); // Support: IE 9 - 10 only
} else if (subWindow.attachEvent) {
subWindow.attachEvent("onunload", unloadHandler);
}
} /* Attributes Judge,将属性支持情况保存在support中,这些属性将在后面的选择器判定中用到
---------------------------------------------------------------------- */
// 这里的assert方法也是依赖函数注入的,旨在判断浏览器是否支持某属性
// 传入的el是一个 document.createElement("fieldset");
support.scope = assert(function (el) {
docElem.appendChild(el).appendChild(document.createElement("div"));
return typeof el.querySelectorAll !== "undefined" &&
!el.querySelectorAll(":scope fieldset div").length;
}); /** ..........................
* ..........................
*/ support.getById = assert(function (el) {
docElem.appendChild(el).id = expando;
return !document.getElementsByName || !document.getElementsByName(expando).length;
}); /* 拦截器、过滤器等组件的注册
---------------------------------------------------------------------- */
if (support.getById) {
Expr.filter["ID"] = function (id) {
var attrId = id.replace(runescape, funescape); // 字符串解码
return function (elem) {
return elem.getAttribute("id") === attrId; // 找到ID
};
};
Expr.find["ID"] = function (id, context) {
if (typeof context.getElementById !== "undefined" && documentIsHTML) {
var elem = context.getElementById(id);
return elem ? [elem] : []; // 存在返回数组,否则返回空数组
}
};
} else { // 如果浏览器不支持获取ID,则利用getAttributeNode()
Expr.filter["ID"] = function (id) {
var attrId = id.replace(runescape, funescape);
return function (elem) {
// 使用 getAttributeNode() 方法从当前元素中通过名称获取属性节点
var node = typeof elem.getAttributeNode !== "undefined" &&
elem.getAttributeNode("id"); 。
return node && node.value === attrId;
};
}; // Support: IE 6 - 7 only
//getElement作为捷径寻找并不是可信赖的
Expr.find["ID"] = function (id, context) {
// 如果context存在ID属性
if (typeof context.getElementById !== "undefined" && documentIsHTML) {
var node, i, elems,
elem = context.getElementById(id); if (elem) {
// Verify the id attribute
node = elem.getAttributeNode("id"); // 一般到这里就return了,
if (node && node.value === id) {
return [elem];
} // fall back on ...
elems = context.getElementsByName(id); // 通过属性name定位元素
i = 0;
while ((elem = elems[i++])) {
node = elem.getAttributeNode("id");
if (node && node.value === id) {
return [elem];
}
}
} return [];
}
};
} // Tag
// p.s.:细心的你会发现后面的Expr对象的filter已经定义了大部分,而find为空,原来是在这里定义
Expr.find["TAG"] = support.getElementsByTagName ?
function (tag, context) {
if (typeof context.getElementsByTagName !== "undefined") {
return context.getElementsByTagName(tag); // DocumentFragment nodes don't have gEBTN
} else if (support.qsa) {
return context.querySelectorAll(tag);
}
} : function (tag, context) {
var elem,
tmp = [],
i = 0, // 巧合的是,一个DocumentFragment节点上也出现了一个gEBTN
results = context.getElementsByTagName(tag); // Filter out possible comments
if (tag === "*") { // 如果是匹配 "*",可能有一些不符合条件的出来
while ((elem = results[i++])) {
if (elem.nodeType === 1) {
tmp.push(elem); // 将results中所有元素类型为1的返回
}
} return tmp;
}
return results;
}; // Class
Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) {
if (typeof context.getElementsByClassName !== "undefined" && documentIsHTML) {
return context.getElementsByClassName(className);
}
}; /* QSA/matchesSelector
---------------------------------------------------------------------- */ // QSA和匹配选择器支持。同上面相同,这里还是做一些准备工作 // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
rbuggyMatches = []; rbuggyQSA = []; // 存储匹配正则表达式的数组 if ((support.qsa = rnative.test(document.querySelectorAll))) { // 构建QSA正则表达式,Regex strategy adopted from Diego Perini
assert(function (el) { var input; docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
"<option selected=''></option></select>"; if (el.querySelectorAll("[msallowcapture^='']").length) {
rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
} // Support: IE8
// Boolean attributes and "value" are not treated correctly
if (!el.querySelectorAll("[selected]").length) {
rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
} if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
rbuggyQSA.push("~=");
} input = document.createElement("input");
input.setAttribute("name", "");
el.appendChild(input);
if (!el.querySelectorAll("[name='']").length) {
rbuggyQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" +
whitespace + "*(?:''|\"\")");
} if (!el.querySelectorAll(":checked").length) {
rbuggyQSA.push(":checked");
} if (!el.querySelectorAll("a#" + expando + "+*").length) {
rbuggyQSA.push(".#.+[+~]");
} el.querySelectorAll("\\\f");
rbuggyQSA.push("[\\r\\n\\f]");
}); assert(function (el) {
el.innerHTML = "<a href='' disabled='disabled'></a>" +
"<select disabled='disabled'><option/></select>"; var input = document.createElement("input");
input.setAttribute("type", "hidden");
el.appendChild(input).setAttribute("name", "D"); // Support: IE8
// Enforce case-sensitivity of name attribute
if (el.querySelectorAll("[name=d]").length) {
rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
} if (el.querySelectorAll(":enabled").length !== 2) {
rbuggyQSA.push(":enabled", ":disabled");
} docElem.appendChild(el).disabled = true;
if (el.querySelectorAll(":disabled").length !== 2) {
rbuggyQSA.push(":enabled", ":disabled");
} el.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
} if ((support.matchesSelector = rnative.test((matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMatchesSelector)))) { assert(function (el) { // 检查是否可以在断开连接的节点上执行检测器
support.disconnectedMatch = matches.call(el, "*"); // 获得el匹配 "*" 的元素 // This should fail with an exception
// Gecko does not error, returns false instead
matches.call(el, "[s!='']:x");
rbuggyMatches.push("!=", pseudos);
});
} rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|"));
rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|")); /* Contains // 包含函数判断,同下面的排序函数相同,单纯的工具函数,原理应该掌握
---------------------------------------------------------------------- */
// compareDocumentPosition,判断一个段落相比较另一个段落的位置:
hasCompare = rnative.test(docElem.compareDocumentPosition); // Element contains another
// Purposefully self-exclusive
// As in, an element does not contain itself
contains = hasCompare || rnative.test(docElem.contains) ?
function (a, b) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!(bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains(bup) :
a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
));
} :
function (a, b) {
if (b) {
while ((b = b.parentNode)) { // 往上回溯,直到b === a或者 b是根节点
if (b === a) {
return true;
}
}
}
return false;
}; /* Sorting // 节点排序函数
---------------------------------------------------------------------- */
// Document order sorting
sortOrder = hasCompare ? // 如果节点是可以比较的
function (a, b) { if (a === b) {
hasDuplicate = true;
return 0;
} var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
if (compare) {
return compare;
} // 如果两者具有相同的根元素,则直接比较位置
compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
a.compareDocumentPosition(b) : // Otherwise we know they are disconnected
1; // Disconnected nodes
if (compare & 1 || // 如果不具有相同根元素,则
(!support.sortDetached && b.compareDocumentPosition(a) === compare)) {
// 如果a是根节点,则返回-1
if (a == document || a.ownerDocument == preferredDoc &&
contains(preferredDoc, a)) {
return -1;
} if (b == document || b.ownerDocument == preferredDoc &&
contains(preferredDoc, b)) {
return 1;
} // Maintain original order
return sortInput ?
// indexof(a,b) 返回b在(类数组)a中的位置
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0;
} return compare & 4 ? -1 : 1;
} :
function (a, b) { // Exit early if the nodes are identical
if (a === b) {
hasDuplicate = true;
return 0;
} var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [a],
bp = [b]; // Parentless nodes are either documents or disconnected
// 没有父亲节点,要么是文档,要么是断开的
if (!aup || !bup) { return a == document ? -1 :
b == document ? 1 :
/* eslint-enable eqeqeq */
aup ? -1 :
bup ? 1 :
sortInput ?
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0; // 如果两者具有相同的父亲
} else if (aup === bup) {
return siblingCheck(a, b);
} // 对比他们所有的祖先
cur = a;
while ((cur = cur.parentNode)) {
ap.unshift(cur); // arr.unshift() 向数组开头添加一个或多个元素,并返回新数组长度
}
cur = b;
while ((cur = cur.parentNode)) {
bp.unshift(cur);
} // Walk down the tree looking for a discrepancy
while (ap[i] === bp[i]) {
i++; // 定位到祖先不同的点
}




词法解析函数:tokenize()

词法分析,从本质上来说使用一些列规定好的拦截器、过滤器来截取我们需要的词,请带着此理念去阅读下面代码

// tokenize两个作用:1.解析选择器;	2.将解析结果存入缓存
tokenize = Sizzle.tokenize = function (selector, parseOnly) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[selector + " "]; // 如果tokenCache中已经有selector了,则直接拿出来就好了
if (cached) {
return parseOnly ? 0 : cached.slice(0);
} soFar = selector;
groups = [];
// 这里的预处理器为了对匹配到的Token适当做一些调整
preFilters = Expr.preFilter; // 循环字符串
while (soFar) { // Comma and first run
if (!matched || (match = rcomma.exec(soFar))) {
if (match) { // Don't consume trailing commas as valid
// 去除soFar的第一个无用的",""
soFar = soFar.slice(match[0].length) || soFar;
}
groups.push((tokens = []));
} matched = false; // Combinators 包含
if ((match = rcombinators.exec(soFar))) {
matched = match.shift(); // 去除第一个元素,并返回此元素的值
tokens.push({
value: matched,
// 将后代组合子投射到空间
type: match[0].replace(rtrim, " ")
});
soFar = soFar.slice(matched.length);
} // Filters 过滤
// 对soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO类型的过滤器方法 for (type in Expr.filter) {
if (// 根据过滤器类型选择正则表达式检验选择器
(match = matchExpr[type].exec(soFar)) &&
// 如果预过滤器不存在此类型或预过滤器处理之后返回了有效值
(!preFilters[type] ||(match = preFilters[type](match)))) {
matched = match.shift(); // 删除第一个元素
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice(matched.length); // 从matched处开启截取,直到最后
}
} if (!matched) {
break;
}
} // 如果只是解析的话,返回解析后的长度,否则抛出错误或返回解析结果
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error(selector) : // Cache the tokens
tokenCache(selector, groups).slice(0); // 从0截取到最后
};

经过上面的分析,可以得到一个结论,那就是除了前面的jQuery对象创建期间的设置的几道拦截方法外,在Sizzle引擎中也对简单结构选择器进行了拦截,并且其使用大量代码来兼容不同的浏览器。

知识点:

  1. 拦截器的注册与使用:
  2. 节点元素的包含与排序:
  3. 选择器字符串词义分析:




结语

如果你输入的只是简单的单体选择器,那么上面讲到的解析过程足以实现。但事实往往并非如此,jQuery最其强大的功能之一就是可以根据复杂多变的选择器以及上下文环境,甚至规定备选种子来选取特定的元素,这就是下一篇要讲到的select()函数




jQuery源码分析系列(二)Sizzle选择器引擎-上的更多相关文章

  1. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  2. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  3. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  4. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  5. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  6. jQuery源码分析系列(三)Sizzle选择器引擎-下

    选择函数:select() 看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用. 简化操作同样根据tokenize ...

  7. jQuery源码分析系列(36) : Ajax - 类型转化器

    什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的 ...

  8. jQuery 源码分析(十二) 数据操作模块 html特性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...

  9. jQuery源码分析系列 : 整体架构

    query这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! ...

  10. jQuery源码分析系列(37) : Ajax 总结

    综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery ...

随机推荐

  1. PHP NULL 合并运算符

    HP 7 新增加的 NULL 合并运算符(??)是用于执行isset()检测的三元运算的快捷方式. NULL 合并运算符会判断变量是否存在且值不为NULL,如果是,它就会返回自身的值,否则返回它的第二 ...

  2. Python os.removedirs() 方法

    概述 os.removedirs() 方法用于递归删除目录.像rmdir(), 如果子文件夹成功删除, removedirs()才尝试它们的父文件夹,直到抛出一个error(它基本上被忽略,因为它一般 ...

  3. Python os.chflags() 方法

    概述 os.chflags() 方法用于设置路径的标记为数字标记.多个标记可以使用 OR 来组合起来.高佣联盟 www.cgewang.com 只支持在 Unix 下使用. 语法 chflags()方 ...

  4. UOJ #22 UR #1 外星人

    LINK:#22. UR #1 外星人 给出n个正整数数 一个初值x x要逐个对这些数字取模 问怎样排列使得最终结果最大 使结果最大的方案数又多少种? n<=1000,x<=5000. 考 ...

  5. layer.js : n.on is not a function

    当时使用的jQuery为1.4.x的版本.换成高版本就好了. 参考 https://blog.csdn.net/marswill/article/details/69316003

  6. Elasticsearch和Scala类型转换

    Scala Type ES Unit null None   null Nil empty array Some[T] according to the table Map object Traver ...

  7. 一文打尽Java继承的相关问题

    相关文章: <面向对象再探究>:介绍了面向对象的基本概念 <详解Java的对象创建>:介绍了对象的创建.构造器的使用 在<面向对象再探究>这篇文章中已经笼统的介绍过 ...

  8. 文字识别还能这样用?通过Python做文字识别到破解图片验证码

    前期准备 1. 安装包,直接在终端上输入pip指令即可: # 发送浏览器请求 pip3 install requests # 文字识别 pip3 install pytesseract # 图片处理 ...

  9. 构建一个拥有sshd服务的docker镜像

    不直接描述结果,通过一个过程探究如何写一个 Dockerfile 一.环境 虚拟机CentOS7.4,Docker1.13.1 二.尝试步骤 1.下载基础镜像 docker pull alpine:3 ...

  10. JS DOM笔记

    js的组成     ECMAScript:JS的语法     DOM:页面文档对象模型     BOM:浏览器对象模型     web APIs     是浏览器提供的一套操作浏览器功能和页面元素的A ...