声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢!

通过Expr.find[ type ]我们找出选择器最右边的最终seed种子合集

通过Sizzle.compile函数编译器,我们把tokenize词法元素编译成闭包函数

超级匹配superMatcher,用佳的方式从seed种子集合筛选出需要的数据

也就是通过seed与compile的匹配,得出最终的结果了


superMatcher 函数

这个方法并不是一个直接定义的方法,通过matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出来的一个curry化的函数,但是最后执行起重要作用的是它。

注意是compile()().

    compile( selector, match )(
seed,
context,
!documentIsHTML,
results,
rsibling.test( selector ) && testContext( context.parentNode ) || context
);

superMatcher方法会根据参数seed 、expandContext和context确定一个起始的查询范围

elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),

有可能是直接从seed中查询过滤,也有可能在context或者context的父节点范围内。如果不是从seed开始,那只能把整个DOM树节点取出来过滤了,把整个DOM树节点取出来过滤了,它会先执行Expr.find["TAG"]( "*", outermost )这句代码等到一个elems集合(数组合集)

context.getElementsByTagName( tag );

可以看出对于优化选择器,最右边应该写一个作用域的搜索范围context比较好

开始遍历这个seed种子合集了

while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context, xml ) ) {
results.push( elem );
break;
}
}

elementMatchers:就是通过分解词法器生成的闭包函数了,也就是“终极匹配器”

为什么是while?

前面就提到了,tokenize选择器是可以用过 “,”逗号分组 group,所以就就会有个合集的概念了

matcher就得到了每一个终极匹配器

通过代码很能看出来matcher方法运行的结果都是bool值

对里面的元素逐个使用预先生成的matcher方法做匹配,如果结果为true的则直接将元素堆入返回结果集里面。


matcher

matcher 就是 elementMatcher函数的包装

整个匹配的核心就在这个里面了

        function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :

我们先来回顾下这个matchers的组合原理

这个地方是最绕的,也是最晕的,所以还是要深入的理解才行哦

先上个简单的流程图:

画的不好 哈哈

执行分解:

第一步:

div > p + div.aaron input[type="checkbox"]

从右边剥离出原生API能使用的接口属性

context.getElementsByTagName( input )

所以找到了input ,因为只可以用 tag是查询,但是此时结果是个合集,引入seed的概念,称之为种子合集

第二步:

div > p + div.aaron [type="checkbox"]'

重组选择器,踢掉input,得到新的tokens词法元素哈希表

第三步:

通过matcherFromTokens函数,然后根据 关系选择器 【'>',"空","~","+"】拆分分组,因为DOM中的节点都是存在关系的,所以引入

Expr.relative -> first:true 两个关系的“紧密”程度, 用于组合最佳的筛选

一次按照如下顺序解析并且编译闭包函数

编译规则:div > p + div.aaron [type="checkbox"]'
编译成4组闭包函数,然后在前后在合并组合成一组
div >

p +

div.aaron 

input[type="checkbox"]

先看构造一组编译函数

A: 抽出div元素, 对应的是TAG类型
B: 通过Expr.filter找到对应匹配的处理器,返回一个闭包处理器
如:TAG方法

        "TAG": function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
function() { return true; } :
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
},

C:将返回的curry方法放入到matchers匹配器组中,继续分解

D:抽出子元素选择器 '>' ,对应的类型 type: ">"

E:通过Expr.relative找到elementMatcher方法分组合并多个词素的的编译函数

    function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}

所以这里其实就是执行了各自Expr.filter匹配中的的判断方法了,看到这里matcher方法原来运行的结果都是bool值,

所以这里只返回了一个组合闭包,通过这个筛选闭包,各自处理自己内部的元素

F:返回的这个匹配器还是不够的,因为没有规范搜索范围的优先级,所以这时候还要引入addCombinator方法

G:根据Expr.relative -> first:true 两个关系的“紧密”程度
如果是是亲密关系addCombinator返回

function( elem, context, xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
return matcher( elem, context, xml );
}
}
}

所以可见如果是紧密关系的位置词素,找到第一个亲密的节点,立马就用终极匹配器判断这个节点是否符合前面的规则

这是第一组终极匹配器的生成流程了

可见过程极其复杂,被包装了三层

依次

addCombinator
elementMatcher
Expr.relative

三个方法嵌套处理出来的结构

然后继续分解下一组,遇到关系选择器又继续依照以上的过程分解

但是有一个不同的地方,下一个分组会把上一个分组给一并合并了

所以整个关系就是一个依赖嵌套很深的结构

最终暴露出来的终极匹配器其实只有一个闭包,但是有内嵌很深的分组闭包了

依照从左边往右依次生成闭包,然后把上一组闭包又push到下一组闭包

就跟栈是一种后进先出的数据结构一样处理了

所以在最外层也就是

type=["checkbox"]

我们回到superMatcher方法的处理了

在遍历seed种子合集,依次匹配matchers闭包函数,传入每一个seed的元素与之匹配(这里就是input),在对应的编译处理器中通过对input的处理,找到最优匹配结果

function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :

这里注意了,是i--,从后往前找
所以第一次开始匹配的就是

check: "checkbox"
name: "type"
operator: "="

那么就找到对应的Attr处理方法

//属性元匹配器工厂
//name :属性名
//operator :操作符
//check : 要检查的值
//例如选择器 [type="checkbox"]中,name="type" operator="=" check="checkbox"
Expr.filter["ATTR"] = function( name, operator, check ) { //返回一个元匹配器
return function( elem ) {
//先取出节点对应的属性值
var result = Sizzle.attr( elem, name ); //看看属性值有木有!
if ( result == null ) {
//如果操作符是不等号,返回真,因为当前属性为空 是不等于任何值的
return operator === "!=";
}
//如果没有操作符,那就直接通过规则了
if ( !operator ) {
return true;
} //转成字符串
result += ""; return
//如果是等号,判断目标值跟当前属性值相等是否为真
operator === "=" ? result === check : //如果是不等号,判断目标值跟当前属性值不相等是否为真
operator === "!=" ? result !== check : //如果是起始相等,判断目标值是否在当前属性值的头部
operator === "^=" ? check && result.indexOf( check ) === 0 : //这样解释: lang*=en 匹配这样 <html lang="xxxxenxxx">的节点
operator === "*=" ? check && result.indexOf( check ) > -1 : //如果是末尾相等,判断目标值是否在当前属性值的末尾
operator === "$=" ? check && result.slice( -check.length ) === check : //这样解释: lang~=en 匹配这样 <html lang="zh_CN en">的节点
operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : //这样解释: lang=|en 匹配这样 <html lang="en-US">的节点
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
//其他情况的操作符号表示不匹配
false;
};
},
Sizzle.attr( elem, name )

传入elem元素就是seed中的input元素,找到是否有'type'类型的属性,

比如

<input type="text">"

所以第一次匹配input就出错了,返回的type是text,而不是我们需要的'checkbox'
这里返回的结果就是false,所以整个之后的处理就直接return了


继续拿出第二个input

继续上一个流程,这时候发现检测到的属性

var result = Sizzle.attr( elem, name );
result: "checkbox"

此时满足第一条匹配,然后继续 i = 0

!matchers[i]( elem, context, xml )

找到第0个编译函数

addCombinator
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
if ( (data = cache[1]) === true || data === cachedruns ) {
return data === true;
}
} else {
cache = outerCache[ dir ] = [ dirkey ];
cache[1] = matcher( elem, context, xml ) || cachedruns;
if ( cache[1] === true ) {
return true;
}
}
}
}
如果是不紧密的位置关系
那么一直匹配到true为止
如祖宗关系的话,就一直找父亲节点直到有一个祖先节点符合规则为止
直接递归调用
matcher( elem, context, xml )

其实就是下一组闭包队列了,传入的上下文是 div.aaron,也就是<input type="checkbox"的父节点

function (elem, context, xml) {
var i = matchers.length;
//从右到左开始匹配
while (i--) {
//如果有一个没匹配中,那就说明该节点elem不符合规则
if (!matchers[i](elem, context, xml)) {
return false;
}
}
return true;
}

依照上面的规则,这样递归下去了,一层一层的匹配


可见它原来不是一层一层往下查,却有点倒回去向上做匹配、过滤的意思。Expr里面只有find和preFilter返回的是集合。

尽管到这里暂时还带着一点疑问,就是最后它为什么用的是逐个匹配、过滤的方法来得到结果集,但是我想Sizzle最基本的“编译原理”应该已经解释清楚了。

哥们,别光看不顶啊!

jQuery 2.0.3 源码分析Sizzle引擎 - 超级匹配的更多相关文章

  1. jQuery 2.0.3 源码分析Sizzle引擎解析原理

    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...

  2. jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则. 简 ...

  3. jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + div.aaron input[type="checkb ...

  4. jQuery 2.0.3 源码分析Sizzle引擎 - 编译函数(大篇幅)

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 从Sizzle1.8开始,这是Sizzle的分界线了,引入了编译函数机制 网上基本没有资料细说这个东东的,sizzle引入这 ...

  5. jQuery 2.0.3 源码分析Sizzle引擎 - 高效查询

    为什么Sizzle很高效? 首先,从处理流程上理解,它总是先使用最高效的原生方法来做处理 HTML文档一共有这么四个API: getElementById 上下文只能是HTML文档 浏览器支持情况:I ...

  6. jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...

  7. jQuery 2.0.3 源码分析core - 选择器

         声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢!      打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象 ...

  8. jQuery 2.0.3 源码分析 Deferred概念

    JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而回调函数中则包含了后续的工作.这也是造成异步编程困难的主要原因:我们一直习惯于“线性”地编写代码 ...

  9. jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on

    事件(Event)是JavaScript应用跳动的心脏,通过使用JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应 事件的基础就不重复讲解了,本来是定位源码分析 ...

随机推荐

  1. 弱省互测#0 t3

    Case 1 题意 要求给出下面代码的答案然后构造输入. 给一个图, n 个点 m 条边 q 次询问,输出所有点对之间最大权值最小的路径. 题解 把每一个询问的输出看成一条边,建一棵最小生成树. Ca ...

  2. 【枚举】POJ 3279

    直达–>POJ 3279 Fliptile 题意:poj的奶牛又开始作孽了,这回他一跺脚就会让上下左右的砖块翻转(1->0 || 0->1),问你最少踩哪些砖块才能让初始的砖块全部变 ...

  3. 新入门node.js必须要知道的概念

    一.对于一个刚入门node.js的朋友来说,一定要了解一些基础概念: 今年我正式进入社会后,发现自己所知道的IT方面的知识,真的只是牛毛,原来人外有人,山外有山,还需要继续努力.下面是一些我的自学习心 ...

  4. 谈 CSS 模块化

    以前看过模块化的相关资料以及解释,对模块化有了一个表皮的了解,自己也做了一些相关的实践,由于接触到的项目交小,所以也没能更好的去体现和理解模块化,但总体还是有那么一些感悟,但是如果要说怎么才能算是好的 ...

  5. Hive_配置远程Metastore

    注 : 待测试 一.准备两三台linux机器,最好是hadoop集群环境 机器A:10.0.0.2 机器B:10.0.0.3 机器C:10.0.0.4 二.个机器安装信息 机器A安装mysql(用于存 ...

  6. *HDU1907 博弈

    John Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submis ...

  7. mysql 连接慢的问题(超过了1秒)

    http://www.cnblogs.com/isenhome/p/5133547.html 症状描述 本机连接mysql速度很快 远程ping mysql主机速度正常 远程连接mysql速度需要等待 ...

  8. canvas初探1

    刚申请的博客,当然这也是第一篇.对于canvas也是刚开始着手进行学习,有哪些不对的地方,还望看到本篇博文的朋友指正. 1.canvas的历史 首先,它是HTML5的一个标签. 它是为了客户端矢量图形 ...

  9. myeclipse中导入js报如下错误Syntax error on token "Invalid Regular Expression Options", no accurate correc

    今天在使用bootstrap的时候引入的js文件出现错误Syntax error on token "Invalid Regular Expression Options", no ...

  10. 非常全面的SQL Server巡检脚本来自sqlskills团队的Glenn Berry 大牛

    非常全面的SQL Server巡检脚本来自sqlskills团队的Glenn Berry 大牛 Glenn Berry 大牛会对这个脚本持续更新 -- SQL Server 2012 Diagnost ...