-
var relative = { ">":{ dir:"parentNode", first: true }, " ":{ dir: "parentNode" }, "+":{ dir:"previousSibling", first: true }, "~":{ dir: "previousSibling" } }
function addCombinator(elems){ var elem; while((elem = elems['parentNode'])){ if(elem.nodeType===1){ return elem; } } }
查看全部 -
CSS选择器的位置关系
1、祖宗和后代 // 用空格 2、父亲和儿子 // 用 > 3、临近兄弟 // 用 + 4、普通兄弟 // 用 ~
查看全部 -
Sizzle超级匹配器的流程:
div > p + div.aaron input[type="checkbox"]
1、从右边剥离出原生API能使用的接口属性:id、class、tagname。所以找到了input,因为只可以用tag是查询,但是此时结果是个合集,引入seed的概念,称之为种子合集。
context.getElementsByTagName( input )
2、重组选择器,踢掉input,得到新的tokens词法元素哈希表
div > p + div.aaron [type="checkbox"]
3、通过matcherFromTokens函数,然后根据关系选择器 【">","空","~","+"】拆分分组,因为DOM中的节点都是存在关系的,所以引入Expr.relative -> first:true 两个关系的“紧密”程度,用于组合最佳的筛选。
4、依次按照如下顺序解析并且编译闭包函数,编译规则:div > p + div.aaron [type="checkbox"]。编译成4组闭包函数,然后在前后在合并组合成一组
div > p + div.aaron input[type="checkbox"]
A. 抽出div元素,对应的是TAG类型
B. 通过Expr.filter找到对应匹配的处理器,返回一个闭包处理器
C. 将返回的curry方法放入到matchers匹配器组中,继续分解
D. 抽出子元素选择器 '>' ,对应的类型 type: ">"
E. 通过Expr.relative找到elementMatcher方法,分组合并多个词素的的编译函数
F:返回的这个匹配器还是不够的,因为没有规范搜索范围的优先级,所以这时候还要引入addCombinator方法
G:根据Expr.relative -> first:true 两个关系的“紧密”程度,找到对应的第一个亲密节点
查看全部 -
elementMatchers:就是通过分解词法器生成的闭包函数了,也就是“终极匹配器
superMatcher遍历seed
while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context, xml ) ) { results.push( elem ); break; } }
为什么是while?
1、前面就提到了,tokenize选择器是可以用过 “,”逗号分组 group,所以就会有个合集的概念了,matcher就得到了每一个终极匹配器。
2、通过代码很能看出来matcher方法运行的结果都是bool值,对里面的元素逐个使用预先生成的matcher方法做匹配,如果结果为true的则直接将元素堆入返回结果集。
查看全部 -
superMatcher函数
1、这个方法并不是一个直接定义的方法,通过matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出来的一个curry化的函数,但是最后执行起重要作用的是它。
2、superMatcher这个方法并不是一个直接定义的方法,通过matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出来的一个curry化的函数,但是最后执行起重要作用的是它
3、superMatcher方法会根据参数seed 、expandContext和context确定一个起始的查询范围:有可能是直接从seed中查询过滤,也有可能在context或者context的父节点范围内。如果不是从seed开始,那只能把整个DOM树节点取出来过滤了,把整个DOM树节点取出来过滤了,它会先执行Expr.find["TAG"]( "*", outermost )这句代码等到一个elems集合(数组合集)
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
查看全部 -
Sizzle编译原理(下)
1、matcherFromTokens,它充当了selector“分词”与Expr中定义的匹配方法的串联与纽带的作用,可以说选择符的各种排列组合都是能适应的了。Sizzle巧妙的就是它没有直接将拿到的“分词”结果与Expr中的方法逐个匹配逐个执行,而是先根据规则组合出一个大的匹配方法,最后一步执行。
2、matcherFromTokens的分解是有规律的,语义节点+关系选择器的组合,Expr.relative 匹配关系选择器类型,当遇到关系选择器时elementMatcher函数将matchers数组中的函数生成一个函数。
3、如果遇到关系选择符就会合并分组了,通过elementMatcher生成一个终极匹配器
4、matcher为当前词素前的“终极匹配器”,combinator为位置词素,根据关系选择器检查,判断elem的兄弟或者父亲节点是否依次符合规则。
5、编译的过程是一个递归深搜的过程。
整个流程总结就干了那么几件事:
1、在Expr.filter找出每一个选择器类型对应的处理方法
2、从右边往左,向父级匹配的时候,注意词素关系,引入relative记录这个映射的关系
3、把对应的处理函数压入matchers数组
整个编译过程,其实粗看就是把函数一层一层包装下去,之后通过匹配器传入对应的种子合集seed一层一层的解开
查看全部 -
每条选择器规则最小的几个单元可以划分为:ATTR | CHILD | CLASS | ID | PSEUDO | TAG
在Sizzle里边有一些工厂方法用来生成对应的这些元匹配器,它就是Expr.filter。
Expr.filter = { ATTR : function (name, operator, check) { CHILD : function (type, what, argument, first, last) { CLASS : function (className) { ID : function (id) { PSEUDO : function (pseudo, argument) { TAG : function (nodeNameSelector) { }
查看全部 -
两层过滤查找:向上迭代查找最近的父级元素
function addCombinator(elems) { var elem; while ((elem = elems['parentNode'])) { if (elem.nodeType === 1) { return elem } } };
查看全部 -
first的意思就是一个快速条件,因为“>”选择器是一个很明确的父子关系所以通过标记first只需要查找一层即可。
function addCombinator(elems) { var elem; // 向上迭代查找 while ((elem = elems['parentNode'])) { if (elem.nodeType === 1) { return elem } } };
查看全部 -
过滤效率问题:
按照解析原理,词法分析,过滤器原理处理之后得到的种子合集通过一次用循环递归去匹配查找,这样的效率是很慢的。
解决方式:
sizzle从给1.8开始就引入了编译的概念,sizzle引入这个编译函数主要的作用是为分词的筛选,提高逐个匹配的效率,实现闭包缓存。
原理:
闭包是js的特性,我们经常会用来作为私有变量的保存处理,那么sizzle就很好的利用了这一特性,把选择器中每一个选择原子都变成了函数的处理方法,然后通过闭包保存着。再缓存在内存中去,这样有重复使用的时候就会首先调用缓存。
查看全部 -
Sizzle过滤器原理(下)
针对选择器的层级关系:
1、首先“>”与“空”是祖辈关系,这样可以理解是线型的,那么我们只要递归检测每次元素的 parentNode 属性返回指定节点的父节点。2、同理“+”与“~”也是类似的兄弟关系,无非就是扩展的范围不同,所以针对层级的关系问题。
jQuery引入了词素关系:
relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }
查看全部 -
过滤处理我们需要考虑的问题:
1 怎么有效的匹配这些选择器的最小判断单元,也就是通过词法分割出后的结果
2 如何处理层级选择器的判断问题过滤是通过一层一层往上回溯不断的循环去查找,这样虽然结果可以拿到,但是效率是非常低的。所以sizzle从1.8后采用了空间换时间的方式,通过把各种过滤器编译成闭包的函数,所以这个过程也可说是"编译函数"。
其实我们看过滤器的就是一个具体的判断方法,通过传递一个上下文元素,来判断是否存在,得到这一个布尔值,这样有效了缓存了重复的处理,来节约判断的过程。
查看全部 -
属性选择器分支的单元结构:
"[input[name=ttt]]" matches = [ 0: "type" 1: "=" 2: "ttt" ] type: "ATTR" value: [name=ttt]"
种子合集:通过三个基本查询,生成种子合集
1、我们从右到左边开始匹配最终合集单元,从左边开始很明显是属性选择器,但是“input[name=ttt]”原生的API是不认识的。这种标签用Expr.find能匹配到了。find:ID、Tag、Class
2、这里引入了seed - 种子合集(搜索器搜到符合条件的标签),放入到这个初始集合seed中。这种我们找到了最终的一个合集,那么我们需要的就是根据剩余的条件筛选出真正的选择器就OK了。找到一个集合后就暂停,不再往下匹配了,因为如果再用这样的方式往下匹配效率就慢了。
查看全部 -
元素的匹配器:
Expr.filter :TAG, ID, CLASS, ATTR, CHILD, PSEUDO
属性选择器有点复杂,通过第一次正则只能匹配器出整体,所以需要第二次分解,引入了Expr.preFilter,Expr.preFilter保留了3个兼容处理分别是ATTR,CHILD,PSEUDO复杂的选择器。
查看全部 -
最终匹配结果的解构:
groups: [ tokens: { matches: ?, type : ?, value : ? }, tokens: { matches: ?, type : ?, value : ? } ]
查看全部 -
什么是词法分析器(tokenize):
词法分析器又称扫描器,词法分析是指将我们编写的文本代码流解析为一个一个的记号,分析得到的记号以供后续语法分析使用
词法分析涉及了3大块
1、分组逗号
2、层级关系
3、每种元素处理
sizzle对于分组过滤处理都用正则,其中都有一个特点,就是都是元字符^开头,限制匹配的初始,所以tokenize也是从左边开始一层一层的剥离。其中会用到的正则有:
//分组 var rcomma = /^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/; //关系符 var rcombinators = /^[\x20\t\r\n\f]*([>+~]|[\x20\t\r\n\f])[\x20\t\r\n\f]*/; //空白 var whitespace = "[\\x20\\t\\r\\n\\f]"; // 字符串要多加个斜线
查看全部 -
\x20 表示空格
\t 表示水平制表符。将当前位置移到下一个tab位置。
\r 表示回车。将当前位置移到本行的开头。
\n 表示回车换行。将当前位置移到下一行的开头。
\f 表示换页。将当前位置移到下一页的开头。
查看全部 -
Sizzle解析原理:
1、浏览器支持高级API时,直接调用querySelectorAll
2、浏览器不支持高级API时,降级通过sizzle处理,那么内部会有一个规则把选择器分组groups,然后通过从右边往左边查找,加入编译函数的方式节约重复查找的性能问题
一个节点跟另一个节点有以下几种关系:
祖宗和后代
父亲和儿子
临近兄弟
普通兄弟在Sizzle里有一个对象是记录跟选择器相关的属性以及操作:Expr。它有以下属性:
relative = { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }
所以在Expr.relative里边定义了一个first属性,用来标识两个节点的“紧密”程度,例如父子关系和临近兄弟关系就是紧密的。在创建位置匹配器时,会根据first属性来匹配合适的节点
查看全部 -
选择器结尾加上「*」就大大降低了这种优势,这也就是很多优化原则提到的尽量避免在选择器末尾添加通配符的原因
查看全部 -
Sizzle设计思路:
我们知道CSS的匹配规则是从右边向左筛选,jQuery在Sizzle中延续了这样的算法,先搜寻页面中所有的a标签,在之后的操纵中再往后判定它的父节点(包括父节点以上)是否为div,一层一层往上过滤,最后返回该操纵序列。
浏览器渲染原理:
1、浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排。各家浏览器引擎的工作原理略有差别,但也有一定规则。
2、简单讲,通常在文档初次加载时,浏览器引擎会解析HTML文档来构建DOM树,之后根据DOM元素的几何属性构建一棵用于渲染的树。渲染树的每个节点都有大小和边距等属性,类似于盒子模型(由于隐藏元素不需要显示,渲染树中并不包含DOM树中隐藏的元素)。
3、当渲染树构建完成后,浏览器就可以将元素放置到正确的位置了,再根据渲染树节点的样式属性绘制出页面。由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成,所以我们知道浏览器最终会将HTML文档(或者说页面)解析成一棵DOM树
浏览器提供的查找接口,基本靠谱的就只有三个:
Expr.find = { 'ID' : context.getElementById, 'CLASS' : context.getElementsByClassName, 'TAG' : context.getElementsByTagName }
查看全部 -
Sizzle选择器
不能不说jQuery的反模式非职责单一深受开发者喜欢,一个接口承载的职责越多内部处理就越复杂了
jQuery查询的对象是dom元素,查询到目标元素后,如何存储?
1、查询的到结果储存到jQuery对象内部,由于查询的dom可能是单一元素,也可能是合集
2、jQuery内部应该要定义一个合集数组,用于存在选择后的dom元素
3、当然啦,根据API,jQuery构建的不仅仅只是DOM元素,还有HTML字符串、Object、[] 等等
源码缩进后的结构:
1、处理""、null、undefined、false、返回this、增加程序的健壮性
2、处理字符串
3、处理DOMElement,返回修改过后的实例对象this
4、处理$(function(){})
查看全部
举报