前言

我们继续从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,// 如果没有传入上下文,则节点类型默认为9nodeType = 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 selectorif ((m = match[1])) {// Document context // 也就是 nodeType === 9if (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替代IDif (newContext !== context || !support.scope) {// Capture the context ID, setting it first if necessaryif ((nid = context.getAttribute("id"))) { // 如果存在,则替换// 字符串解码nid = nid.replace(rcssescape, fcssescape);} else { // 不能存在直接添加属性idcontext.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 selectedif (doc == document || doc.nodeType !== 9 || !doc.documentElement) {return document;}// 更新全局变量document = doc;docElem = document.documentElement;documentIsHTML = !isXML(document);// eslint-disable-next-line eqeqeqif (preferredDoc != document &&(subWindow = document.defaultView) && subWindow.top !== subWindow) {// Support: IE 11, Edgeif (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 attributenode = 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节点上也出现了一个gEBTNresults = context.getElementsByTagName(tag); // Filter out possible commentsif (tag === "*") {                 // 如果是匹配 "*",可能有一些不符合条件的出来while ((elem = results[i++])) {if (elem.nodeType === 1) {tmp.push(elem);       // 将results中所有元素类型为1的返回}}return tmp;}return results;};// ClassExpr.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 Periniassert(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 correctlyif (!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 attributeif (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 insteadmatches.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 itselfcontains = 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 disconnected1;// Disconnected nodesif (compare & 1 || // 如果不具有相同根元素,则(!support.sortDetached && b.compareDocumentPosition(a) === compare)) {// 如果a是根节点,则返回-1if (a == document || a.ownerDocument == preferredDoc && contains(preferredDoc, a)) {return -1;}if (b == document || b.ownerDocument == preferredDoc &&contains(preferredDoc, b)) {return 1;}// Maintain original orderreturn 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 identicalif (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 discrepancywhile (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 runif (!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 tokenstokenCache(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. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

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

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

  5. jQuery源码分析(二)——Sizzle

    在这一章中我们将重点分析jquery的选择器引擎.jquery在3.4版本后,将选择器引擎抽取出来单独放到了Sizzle.js 文件中,本文将基于这个版本来进行分析. 创建缓存 // line 40 ...

  6. jQuery源码分析系列:.domManip() .buildFragment() .clean()

      .domManip(args,table,callback):是jQuery DOM操作的核心函数,可以扩展出如下方法: append/appendTo: prepend/prependTo: b ...

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

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

  8. jQuery源码分析系列:属性操作

    属性操作 1.6.1相对1.5.x最大的改进,莫过于对属性.attr()的重写了.在1.6.1中,将.attr()一分为二: .attr()..prop(),这是一个令人困惑的变更,也是一个破坏性的升 ...

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

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

  10. jQuery源码分析系列(一)初识jQuery

    一个工厂 (function(global, factory){"use strict"// operation_1 })(typedef window !== "und ...

最新文章

  1. git中查看和设置 用户名、密码
  2. 多核分布式队列的实现:“偷”与“自私”的运用(4)
  3. php enable all,php中的enable_dl配置如何有用?
  4. 安卓手机运行git和python操作指南-有手机就能写代码了
  5. ppt android sdk,《0.AndroidSDK概述.ppt
  6. opencv中java的dmatch_关于OpenCV的那些事——ORB的brief描述子(256bit)的match心得
  7. Jsoup实现java模拟登陆
  8. Win10技巧:如何确定电脑是否适用Hyper-V虚拟机?
  9. 非递归中序遍历---算法导论12.1-3
  10. 语音转文字的测试用例
  11. php alt什么意思,img标签的alt属性是什么意思?
  12. 学会了C语言究竟能干什么呢?
  13. Apache Camel入门教程
  14. React写响应式官方网站-媒体查询
  15. python爬贴吧回复_Python爬虫——抓取贴吧帖子
  16. Java编码ASCII、GB2312、GBK、Unicode、UTF-8、UTF-16 编码方式详解
  17. 阿里百秀移动端首页案例
  18. 直播SVGA礼物特效文件如何压缩
  19. vs最新版本以及各个版本的安装注册破解
  20. 在 Excel 中使用 C# .NET 用户定义函数 (UDF)

热门文章

  1. 【车载以太网】【架构】以太网的分层架构_汽车以太网标准化组织介绍
  2. 升级Xcode 10 后报错问题记录([CP] Copy Pods Resources)
  3. 关于知识图谱上下级概念建设的一点想法
  4. 将一个数组分成和尽可能相等的两份
  5. 计算机五大组成部件和工作过程
  6. 豆芽八股专栏之嵌入式
  7. 大功率降压恒流驱动方案 输出电流可做6A
  8. h5 iframe嵌套页面_如何将一个HTML页面嵌套在另一个页面中
  9. JAVA中将两个列表(List)合并为一个列表
  10. 中e管家要不断改变理财方式和心态