Sizzle引擎

摘要:
原因分析至于产生的原因,我们回到代码层面来解释:第一个影响因素:先看Sizzle.find部分for{}Expr.order:["ID","NAME","TAG"];复制代码最初的查找的时候,匹配type的是有一个优先级的ID--˃NAME--˃TAG,对于支持getElementsByClassName的浏览器,Sizzle源码进行了一个处理:在1296行:Expr.order.splice;Expr.find.CLASS=function{if(typeofcontext.getElementsByClassName!第二个影响因素:再看Sizzle.filter部分for{}Expr.filter={CLASS:function{},ID:function{},TAG:function{},CHILD:function{},ATTR:function{},PSEUDO:function{}POS:function{}},复制代码这里与Si

实例说明

先看一个例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<p class="tab" id="a1">1</p>
<p class="tab" id="a2">2</p>
<p class="tab" id="a3">3</p>
<script type="text/javascript" src="../sizzle.js"></script>
<script type="text/javascript">
console.log(Sizzle('.tab:not(:first)')); console.log(Sizzle('p:not(:first)')); console.log(Sizzle('p.tab:not(:first)')); </script>
</body>
</html>

看上面三个结果的三个表达式,估计很多人会觉得结果肯定是一样的,不错,除去IE6/7/8,结果应该都是一样的,结果(一):

    console.log(Sizzle('.tab:not(:first)'));  //[#a2,#a3]    console.log(Sizzle('p:not(:first)'));     //[#a2,#a3]    console.log(Sizzle('p.tab:not(:first)')); //[#a2,#a3]

但是在IE6/7/8下面,结果(二):

    console.log(Sizzle('.tab:not(:first)'));  //[#a1,#a2,#a3]    console.log(Sizzle('p:not(:first)'));     //[#a2,#a3]    console.log(Sizzle('p.tab:not(:first)')); //[#a2,#a3]

其实不仅是IE6/7/8,任何不支持getElementsByClassName方法的浏览器结果都是结果(二)这样。

结果分析

在结果(一)的过程中,

'.tab:not(:first)'选择流程是:
1、document.getElementsByClassName('.tab')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p.tab:not(:first)'选择流程是:
1、document.getElementsByClassName('.tab')获得结果集[#a1,#a2,#a3];
2、过滤p,获得结果集[#a1,#a2,#a3];
3、过滤:not(:first),获得结果集[#a2,#a3];

在结果(二)的过程中:

'.tab:not(:first)'选择流程是:
1、document.getElementsByTagName('*')获得结果集[html,head,body,#a1,#a2,#a3,script,script]
2、过滤:not(:first),获得结果集[head,body,#a1,#a2,#a3,script,script]
3、过滤.tab,获得结果集[#a1,#a2,#a3]
'p:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p.tab:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
3、过滤.tab,获得结果集[#a2,#a3]

可以看到,在不含class的选择符中,两种情况过程是一样的,当含有class的时候,会优先去寻找含有class的元素,从而直接影响了后面过滤步骤的候选集。

原因分析

至于产生的原因,我们回到代码层面来解释:

第一个影响因素:

先看Sizzle.find部分

for ( i = 0, len = Expr.order.length; i < len; i++ ) {}
Expr.order: [ "ID", "NAME", "TAG" ];

最初的查找的时候,匹配type的是有一个优先级的ID-->NAME-->TAG,对于支持getElementsByClassName的浏览器,Sizzle源码进行了一个处理:

在1296行:

Expr.order.splice(1, 0, "CLASS");
Expr.find.CLASS = function( match, context, isXML ) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};

于是在支持getElementsByClassName的浏览器中,Sizzle.find的实现变成

for ( i = 0, len = Expr.order.length; i < len; i++ ) {}
Expr.order: [ "ID","CLASS" "NAME", "TAG" ];

而且CLASS的优先级仅次于ID。

所以.tab:not(:first)在过滤的第一步,不同浏览器的候选集就已经发生了差异。

第二个影响因素:

再看Sizzle.filter部分

for ( type in Expr.filter ) {}
Expr.filter={
CLASS: function( match, curLoop, inplace, result, not, isXML ) {},
ID: function( match ) {},
TAG: function( match, curLoop ) {},
CHILD: function( match ) {},
ATTR: function( match, curLoop, inplace, result, not, isXML ) {},
PSEUDO: function( match, curLoop, inplace, result, not ) {}
POS: function( match ) {}
},

这里与Sizzle.find的一个显著不同是这里用的for..in循环,没有确定的顺序【优先级】,因此并不能保证先过滤什么类型。

所以.tab:not(:first)在先过滤了:not(:first),然后再过滤.tab,所以结果和想想的不一致。

第三个影响因素:

这个涉及到filter的实现,对于含有not的表达式,候选集合会被筛选一遍。

.tab:not(:first)在not部分集合改变,直接去除了第一个元素。

.tab:first 就没有改变,因为first是在POS匹配中处理的。

解决方案

如果我们把Sizzle.filter的实现改成和Sizzle.find一致,变成如下形式:

for ( i = 0, len = Expr.filterOrder.length; i < len; i++ ) {}
Expr.filterOrder: [ "ID","CLASS" "NAME", "TAG" ,"ATTR","CHILD","PSEUDO","POS"];

那么我们就可以保证选择的结果和预期的一致。不过这种牵一发而动全身的事情,显然是不适合的。

另外一个方法就是避免使用这种有歧义的选择符,将:not(:first)作为集合的一个方法调用,而不直接写到选择表达式里面。

另外,如果知道DOM的结构,处理方式就很多了,比如加tag限制,层级限制等等

说明

这种问题并不是出现在没个类似的选择表达式中,具体的情况可以查看Sizzle源码的filter部分。

filter部分比较烦,而且最近没时间写filter部分的东西,有什么错误之处,欢迎交流。

实例说明

先看一个例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<p class="tab" id="a1">1</p>
<p class="tab" id="a2">2</p>
<p class="tab" id="a3">3</p>
<script type="text/javascript" src="../sizzle.js"></script>
<script type="text/javascript">
console.log(Sizzle('.tab:not(:first)')); console.log(Sizzle('p:not(:first)')); console.log(Sizzle('p.tab:not(:first)')); </script>
</body>
</html>

看上面三个结果的三个表达式,估计很多人会觉得结果肯定是一样的,不错,除去IE6/7/8,结果应该都是一样的,结果(一):

    console.log(Sizzle('.tab:not(:first)'));  //[#a2,#a3]    console.log(Sizzle('p:not(:first)'));     //[#a2,#a3]    console.log(Sizzle('p.tab:not(:first)')); //[#a2,#a3]

但是在IE6/7/8下面,结果(二):

    console.log(Sizzle('.tab:not(:first)'));  //[#a1,#a2,#a3]    console.log(Sizzle('p:not(:first)'));     //[#a2,#a3]    console.log(Sizzle('p.tab:not(:first)')); //[#a2,#a3]

其实不仅是IE6/7/8,任何不支持getElementsByClassName方法的浏览器结果都是结果(二)这样。

结果分析

在结果(一)的过程中,

'.tab:not(:first)'选择流程是:
1、document.getElementsByClassName('.tab')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p.tab:not(:first)'选择流程是:
1、document.getElementsByClassName('.tab')获得结果集[#a1,#a2,#a3];
2、过滤p,获得结果集[#a1,#a2,#a3];
3、过滤:not(:first),获得结果集[#a2,#a3];

在结果(二)的过程中:

'.tab:not(:first)'选择流程是:
1、document.getElementsByTagName('*')获得结果集[html,head,body,#a1,#a2,#a3,script,script]
2、过滤:not(:first),获得结果集[head,body,#a1,#a2,#a3,script,script]
3、过滤.tab,获得结果集[#a1,#a2,#a3]
'p:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
'p.tab:not(:first)'选择流程是:
1、document.getElementsByTagName('p')获得结果集[#a1,#a2,#a3];
2、过滤:not(:first),获得结果集[#a2,#a3];
3、过滤.tab,获得结果集[#a2,#a3]

可以看到,在不含class的选择符中,两种情况过程是一样的,当含有class的时候,会优先去寻找含有class的元素,从而直接影响了后面过滤步骤的候选集。

原因分析

至于产生的原因,我们回到代码层面来解释:

第一个影响因素:

先看Sizzle.find部分

for ( i = 0, len = Expr.order.length; i < len; i++ ) {}
Expr.order: [ "ID", "NAME", "TAG" ];

最初的查找的时候,匹配type的是有一个优先级的ID-->NAME-->TAG,对于支持getElementsByClassName的浏览器,Sizzle源码进行了一个处理:

在1296行:

Expr.order.splice(1, 0, "CLASS");
Expr.find.CLASS = function( match, context, isXML ) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};

于是在支持getElementsByClassName的浏览器中,Sizzle.find的实现变成

for ( i = 0, len = Expr.order.length; i < len; i++ ) {}
Expr.order: [ "ID","CLASS" "NAME", "TAG" ];

而且CLASS的优先级仅次于ID。

所以.tab:not(:first)在过滤的第一步,不同浏览器的候选集就已经发生了差异。

第二个影响因素:

再看Sizzle.filter部分

for ( type in Expr.filter ) {}
Expr.filter={
CLASS: function( match, curLoop, inplace, result, not, isXML ) {},
ID: function( match ) {},
TAG: function( match, curLoop ) {},
CHILD: function( match ) {},
ATTR: function( match, curLoop, inplace, result, not, isXML ) {},
PSEUDO: function( match, curLoop, inplace, result, not ) {}
POS: function( match ) {}
},

这里与Sizzle.find的一个显著不同是这里用的for..in循环,没有确定的顺序【优先级】,因此并不能保证先过滤什么类型。

所以.tab:not(:first)在先过滤了:not(:first),然后再过滤.tab,所以结果和想想的不一致。

第三个影响因素:

这个涉及到filter的实现,对于含有not的表达式,候选集合会被筛选一遍。

.tab:not(:first)在not部分集合改变,直接去除了第一个元素。

.tab:first 就没有改变,因为first是在POS匹配中处理的。

解决方案

如果我们把Sizzle.filter的实现改成和Sizzle.find一致,变成如下形式:

for ( i = 0, len = Expr.filterOrder.length; i < len; i++ ) {}
Expr.filterOrder: [ "ID","CLASS" "NAME", "TAG" ,"ATTR","CHILD","PSEUDO","POS"];

那么我们就可以保证选择的结果和预期的一致。不过这种牵一发而动全身的事情,显然是不适合的。

另外一个方法就是避免使用这种有歧义的选择符,将:not(:first)作为集合的一个方法调用,而不直接写到选择表达式里面。

另外,如果知道DOM的结构,处理方式就很多了,比如加tag限制,层级限制等等

说明

这种问题并不是出现在没个类似的选择表达式中,具体的情况可以查看Sizzle源码的filter部分。

filter部分比较烦,而且最近没时间写filter部分的东西,有什么错误之处,欢迎交流。

Prototype源码浅析
一个系列
Sizzle引擎--原生getElementsByClassName对选择结果的影响(jQuery)
posted @2012-02-21 16:53西山 阅读(423) |评论 (5)编辑
Sizzle引擎--原理与实践(四)
posted @2012-02-18 00:32西山 阅读(73) |评论 (0)编辑
Sizzle引擎--原理与实践(三)
posted @2012-02-15 14:26西山 阅读(74) |评论 (0)编辑
Sizzle引擎--原理与实践(二)
posted @2012-02-15 13:46西山 阅读(74) |评论 (2)编辑
Sizzle引擎--原理与实践(一)
posted @2012-02-15 13:45西山 阅读(150) |评论 (1)编辑
Prototype源码浅析——元素选择器部分(一)之$
posted @2012-02-03 15:06西山 阅读(104) |评论 (0)编辑
Prototype源码浅析——Date部分
posted @2012-02-03 15:04西山 阅读(64) |评论 (0)编辑
Prototype源码浅析——Hash部分(一)
posted @2012-01-31 13:27西山 阅读(114) |评论 (0)编辑
Prototype源码浅析——Enumerable部分(二)
posted @2012-01-17 15:09西山 阅读(97) |评论 (0)编辑
Prototype源码浅析——Enumerable部分之each方法
posted @2012-01-16 00:56西山 阅读(49) |评论 (0)编辑
Prototype源码浅析——Number部分
posted @2012-01-14 21:11西山 阅读(53) |评论 (0)编辑
Prototype源码浅析——String部分(四)之补充
posted @2012-01-12 13:11西山 阅读(94) |评论 (0)编辑
Prototype源码浅析——String部分(三)之HTML字符串处理
posted @2012-01-12 10:25西山 阅读(67) |评论 (0)编辑
Prototype源码浅析——String部分(一)之有关indexOf优化
posted @2012-01-10 12:50西山 阅读(78) |评论 (0)编辑

免责声明:文章转载自《Sizzle引擎》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关于使用 VisualVM 进行性能分析及调优union 中null值合并原理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android进入一个新页面,EditText失去焦点并禁止弹出键盘

android在进入一个新页面后,edittext会自动获取焦点并弹出软键盘,这样并不符合用户操作习惯。 在其父控件下,添加如下的属性,就可以完美解决,使其进入页面后不主动获取焦点,并且不弹出软键盘: android:focusable="true"   android:focusableInTouchMode="true" 代码如下: 1 <S...

ElasticSearch索引与搜索

在系列的第一篇文章中我们介绍了ElasticSearch的基本概念和操作,本文将继续介绍ElasticSearch查询和索引功能。 目录: 查询 精确查询 term查询 terms查询 range查询 全文查询 match查询 multi_match查询 script查询 组合查询 bool查询 dis_max查询 function_sc...

Android打造带透明圆弧的ImageView

这几天因为项目需求,需要在ImageView上面叠加一层透明圆弧,并且在沿着圆弧的方向显示相应的文字,效果如下图所示: 拿到这个需求,首先想到的是自定义一个ImageView来实现此功能,即在onDraw()中绘制圆弧和文字。同时因为要保证圆弧的位置可以任意摆放,圆弧的颜色、透明度以及文字大小、颜色等都是可控的,所以增加了一些自定义属性。实现代码非常简单...

mac everything的替代品——fzf使用,速度还是很快的!!!

fzf模糊搜索神器的安装和使用 fzf是一个通用的命令行模糊查找器, 通过输入模糊的关键词就可以定位文件或文件夹。结合其他工具(比如rg)可以完成非常多的工作,在工作中可以大幅提高你的工作效率。 fzf可以用于文件、命令历史记录、进程、主机名、书签、git提交等。 1. fzf使用 1.1 安装 Using Homebrew You can use...

Es使用kibana增删改查以及复杂查询

1.简单操作 1.1 增加 先插入四条数据 PUT /esstudy/user/1 { "name": "张三", "age": 21 } PUT /esstudy/user/2 { "name": "李四", "age": 22 } PUT /esstudy/user/3 { "name": "王五", "age": 23 } P...

MySQL5.5 安装mcafee mysql-audit插件 不成功

主页:https://github.com/mcafee/mysql-audit/wiki 各版本安装包:https://bintray.com/mcafee/mysql-audit-plugin/release 安装步骤还是比较简单的,只有几步: 1)查看插件目录 mysql> SHOW GLOBAL VARIABLES LIKE 'plugin_...