选择函数:select()

看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用。

简化操作同样根据tokenize()函数获得标签数组,然后对标签数组首元素是特殊ID选择器的情况进行了判断,之后对标签数组进行遍历匹配,只有当匹配到正确的选择器seed=find(){}并且token为空,此时才是找到了正确结果。

select = Sizzle.select = function (selector, context, results, seed) {
var i, tokens, token, type, find,
compiled = typeof selector === "function" && selector,
match = !seed && tokenize((selector = compiled.selector || selector)); results = results || []; // 试图简化操作,如果只有一个选择器并且没有种子的时候
if (match.length === 1) {
tokens = match[0] = match[0].slice(0);
// 如果领头的复合选择器是ID选择器
if (tokens.length > 2 && (token = tokens[0]).type === "ID" &&
context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { context = (Expr.find["ID"](token.matches[0]
.replace(runescape, funescape), context) || [])[0];
if (!context) {
return results; // Precompiled matchers will still verify ancestry, so step up a level
} else if (compiled) {
context = context.parentNode;
} selector = selector.slice(tokens.shift().value.length);
} // 一般是i === tokens.length
i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
while (i--) { // 从右往左遍历
token = tokens[i]; // 如果token的类型是相对指示符,则跳出
if (Expr.relative[(type = token.type)]) {
break;
}
// 定位find为函数find(type, context),返回值为元素,
if ((find = Expr.find[type])) { // 搜索区域拓展到其兄弟区域
if (
(seed = find(
token.matches[0].replace(runescape, funescape),
rsibling.test(tokens[0].type) && testContext(context.parentNode) ||
context)
)
) {
// 如果在其兄弟区域找到了此元素,则
tokens.splice(i, 1);
// 如果种子空了,或者tokens空了,则返回
selector = seed.length && toSelector(tokens);
if (!selector) {
push.apply(results, seed);
return results;
} break;
}
}
}
} // Compile and execute a filtering function if one is not provided
// Provide `match` to avoid retokenization if we modified the selector above
// 编译并执行过滤函数(如果没有提供),如果我们修改了上面的选择器,请提供“match”以避免重述
// compile(selector, match)方法调用之后返回的是一个方法:超级匹配器
(compiled || compile(selector, match))(
seed,
context,
!documentIsHTML,
results,
!context || rsibling.test(selector) && testContext(context.parentNode) || context
);
return results;
};




编译函数:compile()

compile()调用则要复杂的多,其首先要根据选择器获得标签数组,针对标签数组内不同元素,其要制定的不同的拦截策略,再将拦截器传入终极匹配器,在终极匹配器中,根据上下文或document找到所有备选种子(DOM元素),用匹配器匹配每一个备选种子,最终得到符合条件的要素。

compile = Sizzle.compile = function (selector, match /* Internal Use Only */) {
var i,
setMatchers = [],
elementMatchers = [],
cached = compilerCache[selector + " "]; // 查看是否存在缓存 if (!cached) { // 创建一个递归检查所有元素的方法
if (!match) {
match = tokenize(selector); // 解析选择器
}
i = match.length;
while (i--) {
// 对每个解析出来的标签配置匹配器
cached = matcherFromTokens(match[i]);
// 根据是否包含伪类,将匹配器分为两组
if (cached[expando]) {
setMatchers.push(cached);
} else {
elementMatchers.push(cached);
}
} // Cache the compiled function
// 缓存编译函数
cached = compilerCache(
selector,
// 将两组匹配器传入,生成终极匹配器superMatche()并返回
matcherFromGroupMatchers(elementMatchers, setMatchers)
); // Save selector and tokenization
cached.selector = selector;
}
// 仔细观察,最后返回的是matcherFromGroupMatchers()的superMatcher()方法
return cached;
};




伪类(集合)选择器:setMatcher()

为什么在compile()函数里,配置选择器时要区分开伪类和普通选择器呢?这是因为伪类和其他原子选择器最大的不同就是其位置影响其发挥作用,因此不能像以前那样从右往左解析,我们必须清楚地知道伪类的位置,然后才能正确得匹配元素。详情请点击此处

一般来说,将包含伪类的选择器分为四个部分:前置选择器伪类选择器伪类并列选择器后置选择器

// 伪类分割器
function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) {
if (postFilter && !postFilter[expando]) {
postFilter = setMatcher(postFilter);
}
if (postFinder && !postFinder[expando]) {
postFinder = setMatcher(postFinder, postSelector);
}
// markFunction()标记方法,用以标注方法是否包含伪类
return markFunction(function (seed, results, context, xml) {
var temp, i, elem,
preMap = [],
postMap = [],
preexisting = results.length, // Get initial elements from seed or context
// 从种子或者上下文得到备选种子
elems = seed || multipleContexts(
selector || "*",
context.nodeType ? [context] : context,
[]
), matcherIn = preFilter && (seed || !selector) ?
// 通过前置匹配器preFilter先过滤出待选种子elems,把过滤结果放入matcherIn中
// preMap存放过滤后的种子集合matcherIn在过滤前的备选种子集合elems中的位置
condense(elems, preMap, preFilter, context, xml) :
elems, matcherOut = matcher ? postFinder || (seed ? preFilter : preexisting || postFilter) ? // ...intermediate processing is necessary
[] : // ...otherwise use results directly
results :
matcherIn; // 如果存在伪类匹配器,使用之。将满足条件节点从matcherIn中取出来存到matcherOut中
// 这个判断阻止伪类并列匹配器和后置匹配器使用setMatcher时进入该分支
if (matcher) {
matcher(matcherIn, matcherOut, context, xml);
} // 然后执行伪类并列匹配器(如果有的话),去除matcherOut中不符合条件的元素
if (postFilter) {
// 将matcherOut拷贝到temp中
temp = condense(matcherOut, postMap);
//执行匹配
postFilter(temp, [], context, xml); // 不匹配的种子全部移出matcherOut,移入matcherIn
i = temp.length;
while (i--) {
if ((elem = temp[i])) {
matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem);
}
}
} // 如果种子不为空
if (seed) {
if (postFinder || preFilter) {
// 最后执行后置匹配器,把matcherOut拷贝一份作为搜索范围context传入后置匹配器中执行,获取到真正的结果
if (postFinder) {
//获取最终matcherOut(把matcherOut置为空后插入到postFinder上下文环境中获取结果)
temp = [];
i = matcherOut.length;
while (i--) {
if ((elem = matcherOut[i])) { //修复matcherIn,因为节点不是最终匹配的结果
temp.push((matcherIn[i] = elem));
}
}
//执行结果获得新的种子放入matcherOut中
postFinder(null, (matcherOut = []), temp, xml);
} // 移动匹配元素从种子到结果来保持同步
i = matcherOut.length;
while (i--) {
if ((elem = matcherOut[i]) &&
(temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) { seed[temp] = !(results[temp] = elem);
}
}
} // 添加元素到结果中,如果定义了后置定位器则使用
} else {
matcherOut = condense(
matcherOut === results ?
matcherOut.splice(preexisting, matcherOut.length) :
matcherOut
);
if (postFinder) {
//将matcherOut作为搜索范围context传入
postFinder(null, results, matcherOut, xml);
} else { // 否则直接应用
push.apply(results, matcherOut);
}
}
});
}




终极匹配器:superMatcher()

此匹配器作为return出来的Curry化函数,最终执行在select()函数的尾部。

superMatcher = function (seed, context, xml, results, outermost) {
var elem, j, matcher,
matchedCount = 0,
i = "0",
unmatched = seed && [],
setMatched = [],
contextBackup = outermostContext, // 确定搜索范围
// 或是参数中传递过来的备选种子seed,或是搜索范围context的所有后代节点
elems = seed || byElement && Expr.find["TAG"]("*", outermost), // Use integer dirruns iff this is the outermost matcher
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
len = elems.length; if (outermost) {
outermostContext = context == document || context || outermost;
} // 遍历所有种子,或上下文所有子孙节点
for (; i !== len && (elem = elems[i]) != null; i++) {
if (byElement && elem) {
j = 0; if (!context && elem.ownerDocument != document) {
setDocument(elem);
xml = !documentIsHTML;
}
//循环执行elementMatchers中的每一组CSS的最终匹配器
while ((matcher = elementMatchers[j++])) {
// 传入三个参数,进行匹配,
if (matcher(elem, context || document, xml)) {
// 完成elementMatchers的元素直接添加到结果中
results.push(elem);
break;
}
}
if (outermost) {
dirruns = dirrunsUnique;
}
} // 跟踪集合匹配器的不匹配元素
if (bySet) {
// They will have gone through all possible matchers
if ((elem = !matcher && elem)) {
matchedCount--;
} // Lengthen the array for every element, matched or not
if (seed) {
unmatched.push(elem);
}
}
} matchedCount += i; if (bySet && i !== matchedCount) {
j = 0;
// 选择器包含伪类的情况下,进入setMatchers匹配器组匹配
while ((matcher = setMatchers[j++])) {
matcher(unmatched, setMatched, context, xml);
} if (seed) { //重返元素匹配,无需进行排序。matchedCount>0表示前面使用elementMatchers过程中有元素有匹配上
//前面使用setMatchers匹配的时候备选种子集合unmatched中某个元素如果有匹配上,该元素在unmatched上的值会被赋值为false
if (matchedCount > 0) {
while (i--) {
// seed中匹配element matcher的element已被push到了results中,如果再把set matcher的匹配结果push到results里,
// 那么results里面element的顺序就和seed不一样了。
// 这段代码就是保证results里的element顺序和seed中的相同,因为setMatched和seed是长度相同且一一对应的数组。
if (!(unmatched[i] || setMatched[i])) {
setMatched[i] = pop.call(results);
}
}
} // 丢弃指数占位符值只得到实际的匹配
setMatched = condense(setMatched);
} //合并匹配结果
push.apply(results, setMatched); // 参数去重并排序
if (outermost && !seed && setMatched.length > 0 &&
(matchedCount + setMatchers.length) > 1) { Sizzle.uniqueSort(results);
}
} // 通过嵌套的匹配覆盖全局的操控
if (outermost) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
} return unmatched;
};

函数Curry化:

函数curry化是一种通过把多个参数填充到函数体中,实现将函数转换为一个新的经过简化的(使之接受的参数更少)函数的技术。

function add(num1){
return function(num2){
return function(num3){
return num1 + num2 + num3;
}
}
}
var f1 = add(2);
var f2 = f1(3);
console.log(f2(5));
console.log(add(2)(3)(5));

在此情况下,可以根据不同的需求获取某阶段的函数对象,再在需要的时候传入对应参数实现链式调用解耦合。




结语

写结语时已经是晚上十一点了,两天时间看了这么多终究是太累了,这一篇给大家完整地介绍了复合选择器的元素获取实现,想趁着今晚写完就免不了出现问题,烦请各位看官大佬评论指正!

下一篇的内容将对Sizzle篇做一个整体回顾,并设计实验对Sizzle选择器引擎进行性能分析,再从不同类选择器语句的性能排序,到出现过的一些精妙逻辑语句分析,最后到代码架构的研究,各位看官不要错过哟!

加油!! 道路是曲折的,前途是光明的!!






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. jQuery源码分析系列(36) : Ajax - 类型转化器

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

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

    前言 我们继续从init()方法中的find()方法往下看, jQuery.find = Sizzle; ... find: function (selector) { /** ... */ ret ...

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

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

  8. jQuery源码分析系列(38) : 队列操作

    Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Qu ...

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

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

随机推荐

  1. C/C++编程笔记:C语言成绩管理系统!链式结构的管理系统源码分享

    最近很多同学因为学校的要求,需要完成自己的那个C语言课程设计,于是就有很多人私信或者加我私聊我,问的最多的还是<学生成绩管理系统>,其实当你项目写多了你就会发现:其实各类的管理系统都离不开 ...

  2. SpringBoot2 整合 Swagger2文档 使用BootstrapUI页面

    SpringBoot2 整合 Swagger2 SpringBoot整合三板斧 第一步.引入pom <dependency> <groupId>com.spring4all&l ...

  3. 痞子衡嵌入式:为下一代智能可穿戴设备而生 - i.MXRT500

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的新品i.MXRT500. 自2018年i.MXRTxxx系列首款芯片i.MXRT600(主打智能语 ...

  4. intel:spectre&Meltdown侧信道攻击(一)

    只要平时对安全领域感兴趣的读者肯定都听过spectre&Meltdown侧信道攻击,今天简单介绍一下这种攻击的原理( https://www.bilibili.com/video/av1814 ...

  5. HashMap源码(数组算法)

    Jdk1.8初始化hashMap容量的算法 static final int tableSizeFor(int cap) { // 先减1,避免传进来的本来就是2的n次幂,导致算出来多了一次幂,比如传 ...

  6. __STL_VOLATILE

    _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__n); _Obj* * __my_free_list ...

  7. 一文入门人工智能的明珠:生成对抗网络(GAN)

    一.简介 在人工智能领域内,GAN是目前最为潮流的技术之一,GAN能够让人工智能具备和人类一样的想象能力.只需要给定计算机一定的数据,它就可以自动联想出相似的数据.我们学习和使用GAN的原因如下: 1 ...

  8. 008_go语言中的Arrays数组

    代码演示 package main import "fmt" func main() { var a [5]int fmt.Println("emp:", a) ...

  9. IDEA的基本使用技巧

    博主在大学里学习的专业是计算机科学与技术,在大三的时候才开始接触 “加瓦”,学习加瓦首先就需要一个运行环境,因为受到了老师们的影响,我第一个编辑JAVA的软件环境便是Eclipse,在学校里学习和使用 ...

  10. java_static、final、super、this关键字的使用

    static关键字 它可以修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象. 当 static 修饰成员变量时,该变量称为类变量 static 数据类型 变量名: 当 sta ...