
第三部分
底层支持模块
第3章 选择器sizzle
第4章 异步队列deferred object
第5章 数据缓存data
第6章 队列queue
第7章 浏览器功能测试support
第3章
选择器sizzle
sizzle是一款纯javascript实现的css选择器引擎,它具有以下特性:
完全独立,无库依赖。
相较于大多数常用选择器其性能非常有竞争力。
压缩和开启gzip后只有4?kb。
具有高扩展性和易于使用的api。
支持多种浏览器,如ie 6.0+、firefox 3.0+、chrome 5+、safari 3+、opera 9+。
w3c selectors api规范定义了方法queryselector()和queryselectorall(),它们用于根据css选择器规范定位文档中的元素,但是老版本的浏览器(如ie 6、ie 7)不支持这两个方法。在sizzle内部,如果浏览器支持方法queryselectorall(),则调用该方法查找元素,如果不支持,则模拟该方法的行为。
sizzle支持几乎所有的css3选择器,并且会按照文档位置返回结果。读者可以访问下面的官网,查看sizzle的文档和示例:
http://api.jquery.com/category/selectors/
http://sizzlejs.com/
使用jquery开发时,大多数时候,总是先调用sizzle查找元素,然后调用jquery方法对查找结果进行操作。此外,sizzle也为jquery事件系统的事件代理提供基础功能(关于事件代理的相关内容请见第9章)。
本章主要介绍和分析sizzle的设计思路、工作原理、源码实现(特别是浏览器不支持方法queryselectorall()的情况),以及jquery对sizzle的整合和扩展。
首先来看看sizzle的总体源码结构。
3.1 总体结构
sizzle的总体源码结构如代码清单3-1所示,为了方便解释,代码中增加了注释:
代码清单3-1 sizzle 的总体源码结构
(function(){
// 选择器引擎入口,查找与选择器表达式 selector 匹配的元素集合
var sizzle = function( selector, context, results, seed ) { ... };
// 工具方法,排序、去重
sizzle.uniquesort = function( results ) { ... };
// 便捷方法,使用指定的选择器表达式 expr 对元素集合 set 进行过滤
sizzle.matches = function( expr, set ) { ... };
// 便捷方法,检查某个元素 node 是否匹配选择器表达式 expr
sizzle.matchesselector = function( node, expr ) { ... };
// 内部方法,对块表达式进行查找
sizzle.find = function( expr, context, isxml ) { ... };
// 内部方法,用块表达式过滤元素集合
sizzle.filter = function( expr, set, inplace, not ) { ... };
// 工具方法,抛出异常
sizzle.error = function( msg ) { ... };
// 工具方法,获取 dom 元素集合的文本内容
var gettext = sizzle.gettext = function( elem ) { ... };
// 扩展方法和属性
var expr = sizzle.selectors = {
// 块表达式查找顺序
order: [ "id", "name", "tag" ],
// 正则表达式集,用于匹配和解析块表达式
match: { id, class, name, attr, tag, child, pos, pseudo },
leftmatch: { ... },
// 属性名修正函数集
attrmap: { "class", "for" },
// 属性值读取函数集
attrhandle: { href, type },
// 块间关系过滤函数集
relative: { "+", ">", "", "~" },
// 块表达式查找函数集
find: { id, name, tag },
// 块表达式预过滤函数集
prefilter: { class, id, tag, child, attr, pseudo, pos },
// 伪类过滤函数集
filters: { enabled, disabled, checked, selected, parent, empty, has, header,
text, radio, checkbox, file, password, submit, image, reset, button, input,
focus },
// 位置伪类过滤函数集
setfilters: { first, last, even, odd, lt, gt, nth, eq },
// 块表达式过滤函数集
filter: { pseudo, child, id, tag, class, attr, pos }
};
// 如果支持方法 queryselectorall(),则调用该方法查找元素
if ( document.queryselectorall ) {
(function(){
var oldsizzle = sizzle;
sizzle = function( query, context, extra, seed ) {
// 尝试调用方法 queryselectorall() 查找元素
// 如果上下文是 document,则直接调用 queryselectorall() 查找元素
return makearray( context.queryselectorall(query), extra );
// 如果上下文是元素,则为选择器表达式增加上下文,然后调用 queryselectorall()
// 查找元素
return makearray( context.queryselectorall( "[id='" + nid + "'] " +
query ), extra );
// 如果查找失败,则仍然调用 oldsizzle()
return oldsizzle(query, context, extra, seed);
};
})();
}
// 如果支持方法 matchesselector(),则调用该方法检查元素是否匹配选择器表达式
(function(){
var matches = html.matchesselector
|| html.mozmatchesselector
|| html.webkitmatchesselector
|| html.msmatchesselector;
// 如果支持方法 matchesselector()
if ( matches ) {
sizzle.matchesselector = function( node, expr ) {
// 尝试调用方法 matchesselector()
var ret = matches.call( node, expr );
return ret;
// 如果查找失败,则仍然调用 sizzle()
return sizzle(expr, null, null, [node]).length > 0;
}
})();
// 检测浏览器是否支持 getelementsbyclassname()
expr.order.splice(1, 0, "class");
expr.find.class = function( match, context, isxml ) { ... };
// 工具方法,检测元素 a 是否包含元素 b
sizzle.contains = function( a, b ) { ... };
})();
代码清单3-1中的变量expr与sizzle.selectors指向了同一个对象,这么做是为了减少拼写字符数、缩短作用域链,并且方便压缩。但是为了直观和避免混淆,本章在描述中统一使用sizzle.selectors。
代码清单3-1中已经介绍了浏览器支持方法queryselectorall()时的查找过程,本章后面的内容将介绍和分析在不支持的情况下,sizzle是如何模拟方法queryselectorall()的行为的。另外,为了简化描述,在后文中把“块表达式查找函数集”“块表达式预过滤函数集”“块表达式过滤函数集”分别简称为“查找函数集”“预过滤函数集”“过滤函数集”。
代码清单3-1中的方法和属性大致可以分为4类:公开方法、内部方法、工具方法、扩展方法及属性。它们之间的调用关系如图3-1所示。
图3-1 sizzle的方法、功能和调用关系
3.2 选择器表达式
为了准确描述sizzle的实现,避免歧义,需要先约定一些相关术语,具体如表3-1所示。
表3-1 术语和约定
序号 术 语 说明和示例
1 选择器表达式 css选择器表达式,例如,"div>p"
2 并列选择器表达式 逗号分割的多个选择器表达式,例如,"div, p"
3 块表达式 例如,"div>p"中的"div"、"p"
4 块表达式类型 例如,"div"的类型是tag,".red"的类型是class,"div.red"则是tag + class。共有8种块表达式类型:id、class、name、attr、tag、child、pos、pseudo
5 块间关系符 表示块表达式之间关系的符号,例如,"div>p"中的">"。共有4种块间关系符:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素
选择器表达式由块表达式和块间关系符组成,如图3-2所示。其中,块表达式分为3种:简单表达式、属性表达式、伪类表达式;块间关系符分为4种:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素;块表达式和块间关系符组成了层级表达式。
图3-2 选择器表达式
3.3 设计思路
在正式开始分析sizzle的源码实现之前,先来讨论和分析下如果要执行一段选择器表达式,或者说设计一个简化版的选择器引擎,需要做些什么工作。下面以"div.red>p"为例来模拟执行过程,具体来说有从左向右查找和从右向左查找两种思路:
1)从左向右:先查找"div.red"匹配的元素集合,然后查找匹配"p"的子元素集合。
2)从右向左:先查找"p"匹配的元素集合,然后检查其中每个元素的父元素是否匹配"div.red"。
无论是从左向右还是从右向左,都必须经历下面3个步骤:
1)首先要能正确地解析出"div.red>p"中的"div.red"、"p"和">",即解析出选择器表达式中的块表达式和块间关系符。这一步是必需的,否则根本无从下手。
2)然后要能正确地找到与"div.red"或"p"匹配的元素集合,即查找单个块表达式的匹配元素集合。以"div.red"为例,可以有两种实现方式:
a.?先查找匹配"div"的元素集合,然后从中过滤出匹配".red"的元素集合。
b.?先查找匹配".red"的元素集合,然后从中过滤出匹配"div"的元素集合。
不管采用以上哪种方式,这个过程都可以分解为两个步骤:第一步用块表达式的一部分进行查找,第二步用块表达式的剩余部分对查找的结果进行过滤。
3)最后来处理"div.red"和"p"之间的关系符">",即处理块表达式之间的父子关系。在这一步骤中,从左向右和从右向左的处理方式是截然不同的:
a.?从左向右:找到"div.red"匹配的元素集合的子元素集合,然后从中过滤出匹配"p"的子元素集合。
b.?从右向左:检查每个匹配"p"的元素的父元素是否匹配"div.red",只保留匹配的元素。
无论采用以上哪种方式,这个过程都可以分解为两个步骤:第一步按照块间关系符查找元素,第二步用块表达式对查找的结果进行过滤。不论元素之间是哪种关系(父子关系、祖先后代关系、相邻的兄弟关系或不相邻的兄弟关系),都可以采用这种方式来查找和过滤。
另外,如果还有更多的块表达式,则重复执行第3步。
对于前面的3个步骤,可以进一步提炼总结,如下:
1)处理选择器表达式:解析选择器表达式中的块表达式和块间关系符。
2)处理块表达式:用块表达式的一部分查找,用剩余部分对查找结果进行过滤。
3)处理块间关系符:按照块间关系符查找,用块表达式对查找结果进行过滤。
从前面对选择器表达式的执行过程的分析,还可以推导分析出以下结论:
从左向右的总体思路是不断缩小上下文,即不断缩小查找范围。
从右向左的总体思路是先查找后过滤。
在从左向右的查找过程中,每次处理块间关系符时都需要处理未知数量的子元素或后代元素,而在从右向左的查找过程中,处理块间关系符时只需要处理单个父元素或有限数量的祖先元素。因此,在大多数情况下,采用从右向左的查找方式其效果要高于从左向右。
在了解了两种执行思路后,现在再来看看sizzle,它是一款从右向左查找的选择器引擎,提供了与前面3个步骤相对应的核心接口:
正则chunker负责从选择器表达式中提取块表达式和块间关系符。
方法sizzle.find( expr, context, isxml )负责查找块表达式匹配的元素集合,方法sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。
对象sizzle.selector.relative中的块间关系过滤函数根据块间关系符过滤元素集合。
函数sizzle( selector, context, results, seed )则按照前面3个步骤将这些核心接口组织起来。
本节对选择器引擎和sizzle的设计思路作了探索和概述,接下来看看sizzle的源码实现。
3.4 sizzle( selector, context, results, seed )
函数sizzle( selector, context, results, seed )用于查找与选择器表达式selector匹配的元素集合。该函数是选择器引擎的入口。
函数sizzle( selector, context, results, seed )执行的6个关键步骤如下:
1)解析块表达式和块间关系符。
2)如果存在位置伪类,则从左向右查找:
a.?查找第一个块表达式匹配的元素集合,得到第一个上下文元素集合。
b.?遍历剩余的块表达式和块间关系符,不断缩小上下文元素集合。
3)否则从右向左查找:
a.?查找最后一个块表达式匹配的元素集合,得到候选集、映射集。
b.?遍历剩余的块表达式和块间关系符,对映射集执行块间关系过滤。
4)根据映射集筛选候选集,将最终匹配的元素放入结果集。
5)如果存在并列选择器表达式,则递归调用sizzle( selector, context, results, seed )查找匹配的元素集合,并合并、排序、去重。
6)最后返回结果集。
下面来看看该函数的源码实现。
1.?定义sizzle( selector, context, results, seed )
相关代码如下所示:
3879 var sizzle = function( selector, context, results, seed ) {
第3879行:定义函数sizzle( selector, context, results, seed ),接受4个参数:
参数selector:css选择器表达式。
参数context:dom元素或文档对象,作为查找元素的上下文,用于限定查找范围。默认值是当前文档对象。
参数results:可选的数组或类数组,函数sizzle( selector, context, results, seed )将把查找到的元素添加到其中。
参数seed:可选的元素集合,函数sizzle( selector, context, results, seed )将从该元素集合中过滤出匹配选择器表达式的元素集合。
2.?修正参数results、context
3880 results = results || [];
3881 context = context || document;
3882
3883 var origcontext = context;
3884
3885 if ( context.nodetype !== 1 && context.nodetype !== 9 ) {
3886 return [];
3887 }
3888
3889 if ( !selector || typeof selector !== "string" ) {
3890 return results;
3891 }
3892
第3880行:如果未传入参数results,则默认为空数组[]。方法.find( selector )调用sizzle ( selector, context, results, seed )时会传入一个jquery对象,匹配元素将会被添加到传入的jquery对象中。
第3881行:如果未传入参数context,则默认为当前document对象。
第3883行:备份上下文context。因为如果参数selector是以#id开头的,可能会把上下文修正为#id所匹配的元素。这里备份的origcontext用于存在并列选择器表达式的情况。
第3885~3887行:如果参数context不是元素,也不是document对象,则忽略本次查询,直接返回空数组[]。
第3889~3891行:如果参数selector是空字符串,或者不是字符串,则忽略本次查询,直接返回传入的参数results。
3.?定义局部变量
3893 var m, set, checkset, extra, ret, cur, pop, i,
3894 prune = true,
3895 contextxml = sizzle.isxml( context ),
3896 parts = [],
3897 sofar = selector;
3898
第3893~3897行:定义一组局部变量,它们的含义和用途如下:
变量m:用于存放正则chunker每次匹配选择器表达式selector的结果。
变量set:在从右向左的查找方式中,变量set称为“候选集”,是最后一个块表达式匹配的元素集合,其他块表达式和块间关系符则会对候选集set进行过滤;对于从左向右的查找方式,变量set是当前块表达式匹配的元素集合,也是下一个块表达式的上下文。
变量checkset:对于从右向左的查找方式,变量checkset称为“映射集”,其初始值是候选集set的副本,其他块表达式和块间关系符则会对映射集checkset进行过滤,过滤时先根据块间关系符将其中的元素替换为父元素、祖先元素或兄弟元素,然后把与块表达式不匹配的元素替换为false,最后根据映射集checkset筛选候选集set;对于从右向左的查找方式,事实上在查找过程中并不涉及变量checkset,只是在函数sizzle()的最后为了统一筛选和合并匹配元素的代码,将变量checkset与变量set指向了同一个数组。
变量extra:用于存储选择器表达式中第一个逗号之后的其他并列选择器表达式。如果存在并列选择器表达式,则会递归调用函数sizzle( selector, context, results, seed )查找匹配元素集合,并执行合并、排序和去重操作。
变量ret:只在从右向左执行方式中用到,用于存放查找器sizzle.find( expr, context, isxml )对最后一个块表达式的查找结果,格式为{ expr:“...”, set: array }。
变量pop:只在从右向左的查找方式中用到,表示单个块表达式。
变量prune:只在从右向左的查找方式中用到,表示候选集set是否需要筛选,默认为true,表示需要筛选,如果选择器表达式中只有一个块表达式,则变量prune为false。
变量contextxml:表示上下文context是否是xml文档。
变量parts:存放了正则chunker从选择器表达式中提取的块表达式和块间关系符。
变量sofar:用于保存正则chunker每次从选择器表达式中提取了块表达式或块间关系符后的剩余部分,初始值为完整的选择器表达式。
4.?解析块表达式和块间关系符
3860 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
3899 // reset the position of the chunker regexp (start from head)
3900 do {
3901 chunker.exec( "" );
3902 m = chunker.exec( sofar );
3903
3904 if ( m ) {
3905 sofar = m[3];
3906
3907 parts.push( m[1] );
3908
3909 if ( m[2] ) {
3910 extra = m[3];
3911 break;
3912 }
3913 }
3914 } while ( m );
3915
第3900~3914行:用正则chunker从选择器表达式中提取块表达式和块间关系符,直到全部提取完毕或遇到下一个并列选择器表达式为止。正则chunker称为“分割器”,含有3个分组:块表达式或块间关系符、逗号、选择器表达式剩余部分,这也是sizzle中最长、最复杂、最关键的正则,具体将在3.5节单独介绍和分析。
第3901~3902行:正则chunker每次匹配选择器表达式的剩余部分之前,先通过匹配一个空字符来重置正则chunker的开始匹配位置,从而使得每次匹配时都会从头开始匹配。直接设置“chunker.lastindex = 0;”也能达到同样的效果。
第3904~3913行:如果正则chunker可以匹配选择器表达式的剩余部分,则将第三个分组(即经过当前匹配后的剩余部分)赋予变量sofar,下次do-while循环时继续匹配;通过这种方式也可过滤掉一些垃圾字符(如空格);同时,将第一个分组中的块表达式或块间关系符插入数组parts中;此外,如果第二个分组不是空字符串,即遇到了逗号,表示接下来是一个并列选择器表达式,则将第三个分组保存在变量extra,然后结束循环。
5.?如果存在位置伪类,则从左向右查找
3916 if ( parts.length > 1 && origpos.exec( selector ) ) {
3917
3918 if ( parts.length === 2 && expr.relative[ parts[0] ] ) {
3919 set = posprocess( parts[0] + parts[1], context, seed );
3920
3921 } else {
3922 set = expr.relative[ parts[0] ] ?
3923 [ context ] :
3924 sizzle( parts.shift(), context );
3925
3926 while ( parts.length ) {
3927 selector = parts.shift();
3928
3929 if ( expr.relative[ selector ] ) {
3930 selector += parts.shift();
3931 }
3932
3933 set = posprocess( selector, set, seed );
3934 }
3935 }
3936
4221 var expr = sizzle.selectors = {
4224 match: {
4231 pos: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
4233 },
4749 };
4751 var origpos = expr.match.pos,
第3916~3935行:如果存在块间关系符(即相邻的块表达式之间有依赖关系)和位置伪类,例如,$('div button:first'),则从左向右查找。正则origpos中定义了所支持的位置伪类,见第4231行。
为什么遇到位置伪类需要从左向右查找呢?以$( "div button:first" )为例,在查找所有div元素下的所有button元素中的第一个时,位置伪类":first"过滤的是"div button"匹配的元素集合,因此,必须从左向右查找,并且需要先从选择器表达式中删除位置伪类,然后执行查找,最后才用位置伪类过滤查找结果。这个过程由函数posprocess( selector, context, seed )实现。
第3918~3919行:如果数组parts中只有两个元素,并且第一个是块间关系符,则可以直接调用函数posprocess( selector, context, seed )查找匹配的元素集合。
第3921~3935行:否则,从左向右对数组parts中的其他块表达式逐个进行查找,每次查找时指定前一个块表达式匹配的元素集合作为上下文,即不断缩小查找范围。
第3922~3924行:首先,从数组parts头部弹出第一个块表达式,递归调用函数sizzle( selector, context, results, seed )查找匹配元素集合,得到第一个上下文元素集合;如果数组parts的第一个元素是块间关系符,则直接把参数context作为第一个上下文元素集合。
第3926~3934行:从左向右遍历数组parts中的其他块表达式和块间关系符,调用函数posprocess( selector, context, seed )查找匹配元素集合,调用时传入的参数selector含有一个块间关系符和一个块表达式,并且指定上下文为前一个块表达式匹配的元素集合set,调用后再将返回值赋值给变量set,作为下一个块表达式的上下文。
posprocess( selector, context, seed )
函数posprocess( selector, context, seed )在指定的上下文数组context下,查找与选择器表达式selector匹配的元素集合,并且支持位置伪类。选择器表达式selector由一个块间关系符和一个块表达式组成。
函数posprocess( selector, context, seed )执行的3个关键步骤如下:
1)删除选择器表达式中的所有伪类。
2)调用sizzle( selector, context, results, seed )查找删除伪类后的选择器表达式所匹配的元素集合。
3)调用 sizzle.filter( expr, set, inplace, not )用伪类过滤查找结果。
下面来看看该函数的源码实现。相关代码如下所示:
5266 var posprocess = function( selector, context, seed ) {
5267 var match,
5268 tmpset = [],
5269 later = "",
5270 root = context.nodetype ? [context] : context;
5271
5272 // position selectors must be done after the filter
5273 // and so must :not(positional) so we move all pseudos to the end
5274 while ( (match = expr.match.pseudo.exec( selector )) ) {
5275 later += match[0];
5276 selector = selector.replace( expr.match.pseudo, "" );
5277 }
5278
5279 selector = expr.relative[selector] ? selector + "*" : selector;
5280
5281 for ( var i = 0, l = root.length; i < l; i++ ) {
5282 sizzle( selector, root[i], tmpset, seed );
5283 }
5284
5285 return sizzle.filter( later, tmpset );
5286 };
第5274~5277行:删除选择器表达式中的所有伪类,并累计在变量 later 中。
第5279行:如果删除伪类后的选择器表达式只剩一个块间关系符,则追加一个通配符"*"。
第5281~5283行:遍历上下文数组,调用函数sizzle( selector, context, results, seed )查找删除伪类后的选择器表达式匹配的元素集合,将查找结果合并到数组tmpset 中。
第5285行:调用方法sizzle.filter( expr, set, inplace, not )用记录的伪类later 过滤元素集合tmpset,并返回一个新数组,其中只包含了过滤后的元素。
下面回到对函数sizzle( selector, context, results, seed )的分析中。
6.?如果不存在位置伪类,则从右向左查找
(1)尝试缩小查找范围
3937 } else {
3938 // take a shortcut and set the context if the root selector is an id
3939 // (but not if it'll be faster if the inner selector is an id)
3940 if ( !seed && parts.length > 1 &&
context.nodetype === 9 &&
!contextxml &&
3941 expr.match.id.test(parts[0]) &&
!expr.match.id.test(parts[parts.length - 1]) ) {
3942
3943 ret = sizzle.find( parts.shift(), context, contextxml );
3944 context = ret.expr ?
3945 sizzle.filter( ret.expr, ret.set )[0] :
3946 ret.set[0];
3947 }
3948
3949 if ( context ) {
// 省略从右向左查找的代码
3982 } else {
3983 checkset = parts = [];
3984 }
3985 }
3986
第3940~3947行:如果第一个块选择器是 id 类型(即格式为#id),并且最后一个块选择器不是id 类型,则修正上下文 context 为第一个块选择器匹配的元素,以缩小查找范围,提高查找效率。在这个过程中,先调用方法 sizzle.find( expr, context, isxml ) 对第一个块表达式执行简单的查找,如果还有剩余部分,再调用方法 sizzle.filter( expr, set, inplace, not ) 对查找结果进行过滤,最后取匹配元素集合的第一个元素作为后续查找的上下文。
第3982~3984行:如果第一个块表达式是id类型,但是没有找到匹配的元素,则没有继续查找和过滤的必要了,此时直接清空数组parts,并设置映射集为空数组。
(2)查找最后一个块表达式匹配的元素集合,得到候选集set、映射集checkset
3950 ret = seed ?
3951 { expr: parts.pop(), set: makearray(seed) } :
3952 sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentnode ? context.parentnode : context, contextxml );
3953
3954 set = ret.expr ?
3955 sizzle.filter( ret.expr, ret.set ) :
3956 ret.set;
3957
3958 if ( parts.length > 0 ) {
3959 checkset = makearray( set );
3960
3961 } else {
3962 prune = false;
3963 }
3964
第3950~3956行:查找最后一个块表达式匹配的元素集合,得到候选集 set。先调用方法sizzle.find( expr, context, isxml )对最后一个块表达式执行简单的查找,如果还有剩余部分,再调用方法sizzle.filter( expr, set, inplace, not )对查找结果进行过滤。
如果传入了参数seed,则不需要调用sizzle.find()查找,只调用sizzle.filter()过滤。
第3958~3963行:如果数组parts中还有其他元素,即还有块表达式或块间关系符,则创建一份候选集set的副本,并赋值给checkset,作为映射集;如果数组parts为空,则表示选择器表达式中只有一个块表达式,此时设置变量prune为false,表示不需要对候选集set进行筛选。
(3)遍历剩余的块表达式和块间关系符,对映射集checkset执行块间关系过滤
3965 while ( parts.length ) {
3966 cur = parts.pop();
3967 pop = cur;
3968
3969 if ( !expr.relative[ cur ] ) {
3970 cur = "";
3971 } else {
3972 pop = parts.pop();
3973 }
3974
3975 if ( pop == null ) {
3976 pop = context;
3977 }
3978
3979 expr.relative[ cur ]( checkset, pop, contextxml );
3980 }
3981
第3965~3980行:从右向左遍历数组 parts 中剩余的块表达式和块间关系符,调用块间关系符在 sizzle.selectors.relative 中对应的过滤函数,对映射集 checkset 执行块间关系过滤,直至数组 parts 为空为止。
第3966~3973行:变量 cur 表示块间关系符,变量pop 表示块间关系符左侧的块表达式。每次遍历时,如果弹出的元素不是块间关系符,则默认为后代关系符;如果弹出的元素是块间关系符,则再弹出一个作为块表达式。因为是从右向左查找,所以变量 pop 的作用是作为过滤映射集checkset 的上下文。
第3975~3977行:如果仍然未找到前一个块表达式 pop,则表示已经到达数组头部,直接将上下文context 作为映射集checkset 的上下文。
第3979行:块间关系过滤函数的参数格式为:
sizzle.selectors.relative[ 块间关系符 cur ]( 映射集 checkset, 左侧块表达式 pop, contextxml );
在块间关系过滤函数中,会先根据块间关系符cur的类型将映射集checkset的元素替换为父元素、祖先元素或兄弟元素,然后将与左侧块表达式pop不匹配的元素替换为false,具体请参见3.8节。
7.?根据映射集checkset筛选候选集set,将最终的匹配元素放入结果集results
3987 if ( !checkset ) {
3988 checkset = set;
3989 }
3990
3991 if ( !checkset ) {
3992 sizzle.error( cur || selector );
3993 }
3994
3995 if ( tostring.call(checkset) === "[object array]" ) {
3996 if ( !prune ) {
3997 results.push.apply( results, checkset );
3998
3999 } else if ( context && context.nodetype === 1 ) {
4000 for ( i = 0; checkset[i] != null; i++ ) {
4001 if ( checkset[i] && (checkset[i] === true || checkset[i].nodetype === 1 && sizzle.contains(context, checkset[i])) ) {
4002 results.push( set[i] );
4003 }
4004 }
4005
4006 } else {
4007 for ( i = 0; checkset[i] != null; i++ ) {
4008 if ( checkset[i] && checkset[i].nodetype === 1 ) {
4009 results.push( set[i] );
4010 }
4011 }
4012 }
4013
4014 } else {
4015 makearray( checkset, results );
4016 }
4017
第3987~3989行:在前面查找匹配元素集合的过程中,如果是从左向右查找的,不会涉及映射集 checkset;如果是从右向左查找的,且只有一个块表达式,也不会对于映射集 checkset 赋值。在这两种情况下,为了统一筛选和合并匹配元素的代码,在这里要先设置映射集 checkset 和候选集set 指向同一个数组。
第3995~4012行:如果映射集 checkset 是数组,则遍历映射集checkset,检查其中的元素是否满足匹配条件,如果满足,则将候选集set 中对应的元素放入结果集results。
第3996~3997行:如果变量 prune 为false,表示不需要筛选候选集set,则直接将映射集checkset 插入结果集results 中。注意这里的隐藏逻辑:当选择器表达式中只有一个块表达式时,才会设置变量 prune 为false,此时映射集checkset 和候选集set 指向同一个数组,见第3958~3963行、第3987~3989行的说明。
第3999~4004行:如果上下文是元素,而不是文档对象,则遍历映射集checkset,如果其中的元素满足以下条件之一,则将候选集set中对应的元素放入结果集results:
是true。
是元素,并且包含在上下文context中。
第4006~4012行:如果上下文是文档对象,则遍历映射集checkset,如果其中的元素满足以下全部条件,则将候选集set中对应的元素放入结果集results:
不是null。
是元素。
第4014~4016行:如果候选集checkset不是数组,则可能是nodelist,这种情况只会在选择器表达式仅仅是简单的标签或类样式(如$( "div" )、$( ".red" ) )时才会出现,此时不需要筛选候选集set,并且映射集checkset和候选集set会指向同一个数组,可以直接将映射集checkset插入结果集results中。见第3958~3963行、第3987~3989行的说明。
8.?如果存在并列选择器表达式,则递归调用sizzle( selector, context, results, seed )查找匹配的元素集合,并合并、排序、去重
4018 if ( extra ) {
4019 sizzle( extra, origcontext, results, seed );
4020 sizzle.uniquesort( results );
4021 }
4022
第4020行:方法sizzle.uniquesort( results )负责对元素集合中的元素排序、去重,具体请参见3.10.1节。
9.?最后返回结果集results
4023 return results;
4024 };
函数sizzle( selector, context, results, seed )的执行过程可以总结为图3-3。
3.5 正则chunker
正则chunker用于从选择器表达式中提取块表达式和块间关系符。该正则是sizzle中最长、最复杂和最关键的正则,图3-4是该正则的分解图,图中包含了每个子块的功能介绍和测试用例。
3.6 sizzle.find( expr, context, isxml )
方法sizzle.find( expr, context, isxml )负责查找与块表达式匹配的元素集合。该方法会按照表达式类型数组sizzle.selectors.order规定的查找顺序(id、class、name、tag)逐个尝试查找,如果未找到,则查找上下文的所有后代元素(*)。
图3-3 sizzle( selector, context, results, seed )的执行过程
方法sizzle.find( expr, context, isxml )执行的5个关键步骤如下:
1)先用正则集合sizzle.selectors.leftmatch中的正则确定表达式类型。
2)然后调用查找函数集sizzle.selectors.find中对应类型的查找函数,查找匹配的元素集合。
3)然后删除块表达式中已查找过的部分。
4)如果没有找到对应类型的查找函数,则读取上下文的所有后代元素。
5)最后返回格式为{set:候选集, expr:块表达式剩余部分}的对象。
下面来看看该方法的源码实现。
1.?定义sizzle.find( expr, context, isxml )
4051 sizzle.find = function( expr, context, isxml ) {
第4051行:定义方法sizzle.find( expr, context, isxml ),它接受3个参数:
参数expr:块表达式。
参数context:dom元素或文档对象,作为查找时的上下文。
参数isxml:布尔值,指示是否运行在一个xml文档中。
2.?遍历表达式类型数组sizzle.selectors.order
4052 var set, i, len, match, type, left;
4053
4054 if ( !expr ) {
4055 return [];
4056 }
4057
4058 for ( i = 0, len = expr.order.length; i < len; i++ ) {
4059 type = expr.order[i];
4060
第4054~4056行:如果块表达式expr是空字符串,则直接返回空数组[]。
第4058行:表达式类型数组sizzle.selectors.order中定义了查找单个块表达式时的顺序,依次是id、class、name、tag,其中,class需要浏览器支持方法getelements
byclassname()。关于sizzle.selectors.order的具体说明请参见3.9.1节。
(1)确定块表达式类型sizzle.selectors.leftmatch[ type ]
4061 if ( (match = expr.leftmatch[ type ].exec( expr )) ) {
4062 left = match[1];
4063 match.splice( 1, 1 );
4064
第4061行:检查每个表达式类型type在sizzle.selectors.leftmatch中对应的正则是否匹配块表达式expr,如果匹配,则可以确定块表达式的类型。
第4062~4063行:对象sizzle.selectors.leftmatch中存放了表达式类型和正则的映射,正则可以用于确定块表达式的类型,并解析其中的参数。它是基于对象sizzle.selectors.match初始化的,具体请参见3.9.2节。
(2)查找匹配元素sizzle.selectors.find[ type ]
4065 if ( left.substr( left.length - 1 ) !== "\\" ) {
4066 match[1] = (match[1] || "").replace( rbackslash, "" );
4067 set = expr.find[ type ]( match, context, isxml );
4068
第4065行:如果匹配正则的内容以反斜杠"\\"开头,表示反斜杠"\\"之后的字符被转义了,不是期望的类型,这时会认为类型匹配失败。
第4066行:过滤其中的反斜杠,以支持将某些特殊字符(例如,“#”、“.”、“[”)作为普通字符使用。举个例子,假设某个input元素的属性id是"a.b",则对应的选择器表达式应该写作$("#a\\.b"),这里替换掉了反斜杠,又会变回"a.b",因此仍然可以通过执行document.getelementsbyid("a.b")查找到input元素。
第4067行:调用表达式类型type在查找函数集sizzle.selectors.find中对应的查找函数,查找匹配的元素集合。sizzle.selectors.find中定义了id、class、name、tag所对应的查找函数,具体请参见3.9.3节。
(3)删除块表达式中已查找过的部分
4069 if ( set != null ) {
4070 expr = expr.replace( expr.match[ type ], "" );
4071 break;
4072 }
4073 }
4074 }
4075 }
4076
第4069~4072行:如果set不是null和undefined,表示对应的查找函数执行了查找操作,则不管有没有找到匹配元素,都将块表达式expr中已查找过的部分删除,并结束方法sizzle.find( expr, context, isxml )的查找过程。
第4070行:用对象sizzle.selectors.match中对应的正则来匹配已查找过的部分,具体请参见3.9.2节。
3.?如果没有找到对应类型的查找函数,则读取上下文的所有后代元素
4077 if ( !set ) {
4078 set = typeof context.getelementsbytagname !== "undefined" ?
4079 context.getelementsbytagname( "*" ) :
4080 [];
4081 }
4082
第4077~4081行:如果set是null或undefined,(大多数情况下)表示没有在sizzle.selectors.find中找到对应的查找函数,例如,$(":input")会读取上下文的所有后代元素作为候选集。
4.?返回{ set:候选集, expr:块表达式剩余部分}
4083 return { set: set, expr: expr };
4084 };
方法sizzle.find( expr, context, isxml )的执行过程可总结为图3-5。
图3-5 sizzle.find( expr, context, isxml )的执行过程
3.7 sizzle.filter( expr, set, inplace, not )
方法sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。在该方法内部,将用过滤函数集sizzle.selectors.filter中的过滤函数来执行过滤操作。
方法sizzle.filter( expr, set, inplace, not )实现的5个关键步骤如下:
1)首先用正则集合sizzle.selectors.leftmatch中的正则确定块表达式类型。
2)然后调用预过滤函数集sizzle.selectors.prefilter中对应类型的预过滤函数,执行过滤前的修正操作。
3)调用过滤函数集sizzle.selectors.filter[ type ]中对应类型的过滤函数,执行过滤操作,如果过滤函数返回false,则把元素集合中对应位置的元素替换为false。
4)最后删除块表达式中已过滤的部分。
5)重复第1)~4)步,直至块表达式变为空字符串。
1.定义sizzle.filter( expr, set, inplace, not )
4086 sizzle.filter = function( expr, set, inplace, not ) {
第4086行:定义方法sizzle.filter( expr, set, inplace, not ),它接受 4 个参数:
参数set:待过滤的元素集合。
参数inplace:布尔值。如果为true,则将元素集合set中与选择器表达式不匹配的元素设置为false;如果不为true,则重新构造一个元素数组并返回,只保留匹配元素。
参数not:布尔值。如果为true,则去除匹配元素,保留不匹配元素;如果不为true,则去除不匹配元素,保留匹配元素。
2.?用块表达式expr过滤元素集合set,直到expr为空
4087 var match, anyfound,
4088 type, found, item, filter, left,
4089 i, pass,
4090 old = expr,
4091 result = [],
4092 curloop = set,
4093 isxmlfilter = set && set[0] && sizzle.isxml( set[0] );
4094
4095 while ( expr && set.length ) {
第4095行:用块表达式expr过滤元素集合set,直到expr变为空字符串。如果候选集set变为空数组,则没有必要继续执行过滤操作。
(1)遍历块过滤函数集sizzle.selectors.filter
4096 for ( type in expr.filter ) {
第4096行:遍历块过滤函数集sizzle.selectors.filter,调用其中的过滤函数执行过滤操作。块过滤函数集sizzle.selectors.filter中定义了pseudo、child、id、tag、class、attr、pos对应的过滤函数,具体请参见3.9.7节。
(2)确定块表达式类型sizzle.selectors.leftmatch[ type ]
4097 if ( (match = expr.leftmatch[ type ].exec( expr )) != null && match[2] ) {
4098 filter = expr.filter[ type ];
4099 left = match[1];
4100
4101 anyfound = false;
4102
4103 match.splice(1,1);
4104
4105 if ( left.substr( left.length - 1 ) === "\\" ) {
4106 continue;
4107 }
4108
第4097行:检查每个表达式类型type在sizzle.selectors.leftmatch中对应的正则是否匹配块表达式expr,如果匹配,则可以确定块表达式的类型。
第4105~4107行:如果匹配正则的内容以反斜杠"\\"开头,表示反斜杠"\\"之后的字符被转义了,不是期望的类型,这时会认为类型匹配失败。
(3)调用预过滤函数sizzle.selectors.prefilter[ type ]
4109 if ( curloop === result ) {
4110 result = [];
4111 }
4112
4113 if ( expr.prefilter[ type ] ) {
4114 match = expr.prefilter[ type ]( match, curloop, inplace, result, not, isxmlfilter );
4115
4116 if ( !match ) {
4117 anyfound = found = true;
4118
4119 } else if ( match === true ) {
4120 continue;
4121 }
4122 }
4123
第4109~4111行:用于缩小候选集。
第4113~4122行:如果在预过滤函数集sizzle.selectors.prefilter中存在对应的预过滤函数,则调用,执行过滤前的修正操作。预过滤函数负责进一步修正过滤参数,具体请参见3.9.4节。
第4116~4121行:预过滤函数有3种返回值:
false:已经执行了过滤,缩小了候选集,例如,class。
true:需要继续执行预过滤,尚不到执行过滤函数的时候,例如,pos、child。
字符串:修正后的过滤参数(通常是块表达式),后面会继续调用对应的过滤函数。
(4)调用过滤函数sizzle.selectors.filter[ type ]
4124 if ( match ) {
4125 for ( i = 0; (item = curloop[i]) != null; i++ ) {
4126 if ( item ) {
4127 found = filter( item, match, i, curloop );
4128 pass = not ^ found;
4129
4130 if ( inplace && found != null ) {
4131 if ( pass ) {
4132 anyfound = true;
4133
4134 } else {
4135 curloop[i] = false;
4136 }
4137
4138 } else if ( pass ) {
4139 result.push( item );
4140 anyfound = true;
4141 }
4142 }
4143 }
4144 }
4145
第4124~4144行:遍历元素集合curloop,对其中的每个元素执行过滤函数,检测元素是否匹配。
第4127~4128行:变量found表示当前元素是否匹配过滤表达式;变量pass表示当前元素item是否可以通过过滤表达式的过滤。如果变量found为true,表示匹配,此时如果未指定参数not,则变量pass为true;如果变量found为false,表示不匹配,此时如果参数not为true,则变量pass为true;其他情况下,变量pass为false。
第4130~4141行:如果参数inplace为true,则将与块表达式expr不匹配的元素设置为false;如果参数inplace不是true,则重新构造一个元素数组,只保留匹配元素,即会不断地缩小元素集合。
(5)删除块表达式expr中已过滤的部分
4146 if ( found !== undefined ) {
4147 if ( !inplace ) {
4148 curloop = result;
4149 }
4150
4151 expr = expr.replace( expr.match[ type ], "" );
4152
4153 if ( !anyfound ) {
4154 return [];
4155 }
4156
4157 break;
4158 }
4159 }
4160 }
4161
第4146行:变量found是过滤函数sizzle.selectors.filter[ type ]的返回值,如果不等于undefined,表示至少执行过一次过滤。大多数情况下,过滤操作发生在过滤函数中,不过也可能发生在预过滤函数中,例如,class、pos、child。
第4147~4149行:如果参数inplace不是true,则将新构建的元素数组赋值给变量curloop,在下次循环时,会将result再次置为空数组(见第4109~4111行),然后存放通过过滤的元素(见第4130~4141行),然后再赋值给变量curloop,即会不断地缩小元素集合。
第4151行:删除块表达式中已过滤过的部分,直至块表达式变为空字符串。用对象sizzle.selectors.match中对应的正则匹配已过滤过的部分,具体请参见3.9.2节。
第4153~4155行:如果没有找到可以通过过滤的元素,直接返回一个空数组。
(6)如果块表达式没有发生变化,则认为不合法
4162 // improper expression
4163 if ( expr === old ) {
4164 if ( anyfound == null ) {
4165 sizzle.error( expr );
4166
4167 } else {
4168 break;
4169 }
4170 }
4171
4172 old = expr;
4173 }
4174
4178 sizzle.error = function( msg ) {
4179 throw new error( "syntax error, unrecognized expression: " + msg );
4180 };
第4163~4172行:如果块表达式expr没有发生变化,说明前面的过滤没有生效,动不了块表达式expr分毫,此时如果没有找到可以通过过滤的元素,则认为块表达式expr不合法,抛出语法错误的异常。
3.?返回过滤后的元素集合,或缩减范围后的元素集合
4175 return curloop;
4176 };
方法sizzle.filter( expr, set, inplace, not )的执行过程可以总结为图3-6。
3.8 sizzle.selectors.relative
对象sizzle.selectors.relative中存放了块间关系符和对应的块间关系过滤函数,称为“块间关系过滤函数集”。
块间关系符共有4种,其含义和过滤方式如表3-2所示。
图3-6 sizzle.filter( expr, set, inplace, not )的执行过程
表3-2 块间关系符的含义和过滤方式
序号 块间关系符 选择器表达式 说 明 从右向左的过滤方式
1 "" ancestor descendant 匹配所有后代元素 检查祖先元素是否匹配左侧的块表达式
2 "+" prev + next 匹配下一个兄弟元素 检查前一个兄弟元素否匹配左侧的块表达式
3 ">" parent > child 匹配所有子元素 检查父元素是否匹配左侧的块表达式
4 "~" prev~siblings 匹配之后的所有兄弟元素 检查之前的兄弟元素是否匹配左侧的块表达式
在函数sizzle( selector, context, results, seed )从右向左进行过滤时,块间关系过滤函数被调用,用于检查映射集checkset中的元素是否匹配块间关系符左侧的块表达式。调用时的参数格式为:
块间关系过滤函数接受3个参数:
参数checkset:映射集,对该元素集合执行过滤操作。
参数part:大多数情况下是块间关系符左侧的块表达式,该参数也可以是dom元素。
块间关系过滤函数实现的3个关键步骤如下:
1)遍历映射集checkset。
2)按照块间关系符查找每个元素的兄弟元素、父元素或祖先元素。
3)检查找到的元素是否匹配参数part,并替换映射集checkset中对应位置的元素。
a.?如果参数part是标签,则检查找到的元素其节点名称nodename是否与之相等,如果相等则替换为找到的元素,不相等则替换为false。
b.?如果参数part是dom元素,则检查找到的元素是否与之相等,如果相等则替换为true,不相等则替换为false。
c.?如果参数part是非标签字符串,则调用方法sizzle.filter( selector, set, inplace, not )过滤。
也就是说,遍历结束后,映射集checkset中的元素可能会是兄弟元素、父元素、祖先元素、true或false。
3.8.1 "+"
块间关系符"+"匹配选择器"prev + next",即匹配所有紧接在元素prev后的兄弟元素next。例如,$("div + span")、$(".lastdiv + span")。对于从右向左的查找方式,则是检查元素next之前的兄弟元素是否匹配块表达式prev。
4251 relative: {
4252 "+": function(checkset, part){
4253 var ispartstr = typeof part === "string",
4254 istag = ispartstr && !rnonword.test( part ),
4255 ispartstrnottag = ispartstr && !istag;
4256
4257 if ( istag ) {
4258 part = part.tolowercase();
4259 }
4260
4261 for ( var i = 0, l = checkset.length, elem; i < l; i++ ) {
4262 if ( (elem = checkset[i]) ) {
4263 while ( (elem = elem.previoussibling) && elem.nodetype !== 1 ) {}
4264
4265 checkset[i] = ispartstrnottag || elem && elem.node
name.tolowercase() === part ?
4266 elem || false :
4267 elem === part;
4268 }
4269 }
4270
4271 if ( ispartstrnottag ) {
4272 sizzle.filter( part, checkset, true );
4273 }
4274 },
4338 },
第4253~4255行:定义一组局部变量,它们的含义和用途如下:
变量ispartstr:指示参数part是否是字符串。
变量istag:指示参数part是否为标签字符串。
变量ispartstrnottag:指示参数part是否是非标签字符串。
第4261~4269行:遍历映射集checkset,查找每个元素的前一个兄弟元素,并替换映射集checkset中对应位置的元素,有以下3个逻辑分支:
如果未找到兄弟元素,则替换为false。
如果找到了兄弟元素,并且参数part是标签,则检查兄弟元素的节点名称nodename是否与之相等,如果相等则替换为兄弟元素,不相等则替换为false。
如果找到了兄弟元素,并且参数part是dom元素,则检查二者是否相等,如果相等则替换为true,不相等则替换为false。
因此,在遍历结束后,映射集checkset中的元素可能会是兄弟元素、true或false。
第4263行:在遍历兄弟元素的同时过滤掉非元素节点,并且只要取到一个兄弟元素就退出while循环。
第4271~4273行:如果参数part是非标签字符串,则调用方法sizzle.filter( selector, set, inplace, not )过滤映射集checkset。对于参数part是标签和dom元素的情况,在前面遍历映射集checkset时已经处理过了。
3.8.2 ">"
块间关系符">"用于选择器"parent > child",即匹配父元素parent下的子元素child。例如,$("div + span")、$(".lastdiv + span")。对于从右向左的查找方式,则是检查子元素child的父元素是否匹配块表达式parent。
4276 ">": function( checkset, part ) {
4277 var elem,
4278 ispartstr = typeof part === "string",
4279 i = 0,
4280 l = checkset.length;
4281
4282 if ( ispartstr && !rnonword.test( part ) ) {
4283 part = part.tolowercase();
4284
4285 for ( ; i < l; i++ ) {
4286 elem = checkset[i];
4287
4288 if ( elem ) {
4289 var parent = elem.parentnode;
4290 checkset[i] = parent.nodename.tolowercase() === part ? parent : false;
4291 }
4292 }
4293
4294 } else {
4295 for ( ; i < l; i++ ) {
4296 elem = checkset[i];
4297
4298 if ( elem ) {
4299 checkset[i] = ispartstr ?
4300 elem.parentnode :
4301 elem.parentnode === part;
4302 }
4303 }
4304
4305 if ( ispartstr ) {
4306 sizzle.filter( part, checkset, true );
4307 }
4308 }
4309 },
第4282~4292行:如果参数part是标签,则遍历映射集checkset,查找每个元素的父元素,并检查父元素的节点名称nodename是否与参数part相等,如果相等则替换映射集checkset中对应位置的元素为父元素,不相等则替换为false。
第4294~4307行:如果参数part不是标签,则可能是非标签字符串或dom元素,同样遍历映射集checkset,查找每个元素的父元素,并替换映射集checkset中对应位置的元素,在这个过程中有以下2个逻辑分支:
如果参数part是非标签字符串,则在遍历映射集checkset的过程中,替换映射集checkset中对应位置的元素为父元素,遍历结束后调用方法sizzle.filter( selector, set, inplace, not )过滤映射集checkset。
如果参数part是元素,则在遍历映射集checkset时,检查每个元素的父元素是否与之相等,如果相等则替换映射集checkset中对应位置的元素为true,不相等则替换为false。
因此,在遍历结束后,映射集checkset中的元素可能会是父亲元素、true或false。
3.8.3 ""
块间关系符""用于选择器"ancestor descendant",即匹配祖先元素ancestor的所有后代元素descendant。例如,$("div button")、$("div .btn")。对于从右向左的查找方式,则是检查后代元素descendant的祖先元素是否匹配块表达式ancestor。
4311 "": function(checkset, part, isxml){
4312 var nodecheck,
4313 donename = done++,
4314 checkfn = dircheck;
4315
4316 if ( typeof part === "string" && !rnonword.test( part ) ) {
4317 part = part.tolowercase();
4318 nodecheck = part;
4319 checkfn = dirnodecheck;
4320 }
4321
4322 checkfn( "parentnode", part, donename, checkset, nodecheck, isxml );
4323 },
第4312~4322行:这段代码含有2个逻辑分支:
如果参数part是非标签字符串或dom元素,则调用函数dircheck()过滤映射集checkset。
如果参数part是标签,则调用函数dirnodecheck()过滤映射集checkset。
调用函数dircheck()和dirnodecheck()时的参数格式为:
checkfn( 方向 "parentnode/previoussibling", 块表达式 part, 缓存计数器 donename, 映射集 checkset, nodecheck, isxml )
函数dircheck()和dirnodecheck()会遍历映射集checkset,查找每个元素的祖先元素,并检查是否有祖先元素匹配参数part,同时替换映射集checkset中对应位置的元素。具体请参见3.8.5节和3.8.6节。
3.8.4 "~"
块间关系符"~"用于选择器"prev~siblings",即匹配元素prev之后的所有兄弟元素siblings。例如,$('div~p')。对于从右向左的查找方式,则是检查元素siblings之前的兄弟元素是否匹配块表达式prev。
sizzle.selectors.relative["~"]( checkset, part )的源码实现与sizzle.selectors.relative[""]( checkset, part )几乎一样,两者的区别仅仅在于调用函数dircheck()和dirnodecheck()时第一个参数的值不同,前者是"previoussibling",后者则是"parentnode"。
4221 var expr = sizzle.selectors = {
4325 "~": function( checkset, part, isxml ) {
4326 var nodecheck,
4327 donename = done++,
4328 checkfn = dircheck;
4329
4330 if ( typeof part === "string" && !rnonword.test( part ) ) {
4331 part = part.tolowercase();
4332 nodecheck = part;
4333 checkfn = dirnodecheck;
4334 }
4335
4336 checkfn( "previoussibling", part, donename, checkset, nodecheck, isxml );
4337 }
3.8.5 dircheck( dir, cur, donename, checkset, nodecheck, isxml )
函数dircheck( dir, cur, donename, checkset, nodecheck, isxml )负责遍历候选集checkset,检查其中每个元素在某个方向dir上是否有与参数cur匹配或相等的元素。如果找到,则将候选集checkset中对应位置的元素替换为找到的元素或true;如果未找到,则替换为false。
在块间关系过滤函数sizzle.selectors.relative[""/"~"]( checkset, part )中,当参数part是非标签字符串或dom元素时,才会调用函数dircheck()。
5201 function dircheck( dir, cur, donename, checkset, nodecheck, isxml ) {
5202 for ( var i = 0, l = checkset.length; i < l; i++ ) {
5203 var elem = checkset[i];
5204
5205 if ( elem ) {
5206 var match = false;
5207
5208 elem = elem[dir];
5209
5210 while ( elem ) {
5211 if ( elem[ expando ] === donename ) {
5212 match = checkset[elem.sizset];
5213 break;
5214 }
5215
5216 if ( elem.nodetype === 1 ) {
5217 if ( !isxml ) {
5218 elem[ expando ] = donename;
5219 elem.sizset = i;
5220 }
5221
5222 if ( typeof cur !== "string" ) {
5223 if ( elem === cur ) {
5224 match = true;
5225 break;
5226 }
5227
5228 } else if ( sizzle.filter( cur, [elem] ).length > 0 ) {
5229 match = elem;
5230 break;
5231 }
5232 }
5233
5234 elem = elem[dir];
5235 }
5236
5237 checkset[i] = match;
5238 }
5239 }
5240 }
第5201行:定义函数dircheck( dir, cur, donename, checkset, nodecheck, isxml ),它接受6个参数:
参数dir:表示查找方向的字符串,例如,“parentnode”、“previoussibling”。
参数cur:大多数情况下是非标签字符串格式的块表达式,也可能是dom元素。
参数donename:数值。本次查找的唯一标识,用于优化查找过程,避免重复查找。
参数checkset:候选集,在查找过程中,其中的元素将被替换为父元素、祖先元素、兄弟元素、true或false。
参数nodecheck:undefined。在后面的代码中没有用到该参数。
第5202~5239行:遍历候选集checkset,对其中的每个元素,沿着某个方向(例如,“parentnode”、“previoussibling”)一直查找,直到找到与参数cur(dom元素)相等或者与参数cur(非标签字符串)匹配的元素为止,或者直到在该方向上不再有元素为止。
如果找到与参数cur(dom元素)相等的元素,则替换映射集checkset中对应位置的元素为true;如果找到与参数cur(非标签字符串)匹配的元素,则替换为找到的元素;如果未找到,则默认替换为false。
第5211~5220行:在查找过程中,如果遇到已经检查过的元素,则直接取该元素在候选集checkset中对应位置上的元素,避免重复查找。
第5222~5231行:如果参数cur是dom元素,则直接检查找到的元素是否与之相等;如果参数cur是非标签字符串,则调用方法sizzle.filter( expr, set, inplace, not )检查是否与之匹配。
第5237行:替换映射集checkset中对应位置的元素。变量match的初始值为false;如果找到与参数cur(dom元素)相等的元素,则其值变为true;如果找到与参数cur(非标签字符串)匹配的元素,则其值变为找到的元素。
3.8.6 dirnodecheck( dir, cur, donename, checkset, nodecheck, isxml )
函数dirnodecheck( dir, cur, donename, checkset, nodecheck, isxml )负责遍历候选集checkset,检查其中每个元素在某个方向dir上是否有与参数cur匹配的元素。如果找到,则将候选集checkset中对应位置的元素替换为找到的元素;如果未找到,则替换为false。
在块间关系过滤函数sizzle.selectors.relative[""/"~"]( checkset, part )中,当参数part是标签时,才会调用函数dirnodecheck()。
5168 function dirnodecheck( dir, cur, donename, checkset, nodecheck, isxml ) {
5169 for ( var i = 0, l = checkset.length; i < l; i++ ) {
5170 var elem = checkset[i];
5171
5172 if ( elem ) {
5173 var match = false;
5174
5175 elem = elem[dir];
5176
5177 while ( elem ) {
5178 if ( elem[ expando ] === donename ) {
5179 match = checkset[elem.sizset];
5180 break;
5181 }
5182
5183 if ( elem.nodetype === 1 && !isxml ){
5184 elem[ expando ] = donename;
5185 elem.sizset = i;
5186 }
5187
5188 if ( elem.nodename.tolowercase() === cur ) {
5189 match = elem;
5190 break;
5191 }
5192
5193 elem = elem[dir];
5194 }
5195
5196 checkset[i] = match;
5197 }
5198 }
5199 }
第5168行:定义函数dirnodecheck( dir, cur, donename, checkset, nodecheck, isxml ),它接受6个参数:
参数cur:标签字符串。
参数checkset:候选集,在查找过程中,其中的元素将被替换为与参数cur匹配的元素或false。
参数nodecheck:标签字符串。在后边的代码中没有用到该参数。
第5169~5198行:遍历候选集checkset,对其中的每个元素,沿着某个方向(例如,“parentnode”、“previoussibling”)一直查找,直到找到节点名称nodename与参数cur相等的元素,或者在该方向上不再有元素为止。如果找到,则替换映射集checkset中对应位置的元素为找到的元素;如果未找到,则默认替换为false。
第5178~5186行:在查找过程中,如果遇到已经检查过的元素,则直接取该元素在候选集checkset中对应位置上的元素,避免重复查找。
第5196行:替换映射集checkset中对应位置的元素。变量match初始值为false,如果找到节点名称nodename与参数cur相等的元素,则其值变为找到的元素。
3.9 sizzle.selectors
对象sizzle.selectors包含了sizzle在查找和过滤过程中用到的正则、查找函数、过滤函数,其中包含的属性见图3-1,源码结构见代码清单3-1。
3.9.1 sizzle.selectors.order
表达式类型数组sizzle.selectors.order中定义了查找单个块表达式时的查找顺序,依次是id、class、name、tag。其中,class需要浏览器支持方法getelementsbyclass
name()。查找顺序综合考虑了浏览器是否支持、查找结果集的大小、查找效率、使用频率等因素。
4222 order: [ "id", "name", "tag" ],
5139 (function(){
5140 var div = document.createelement("div");
5141
5142 div.innerhtml = "<div class='test e'></div><div class='test'></div>";
5143
5144 // opera can't find a second classname (in 9.6)
5145 // also, make sure that getelementsbyclassname actually exists
5146 if ( !div.getelementsbyclassname || div.getelementsbyclassname("e").length === 0 ) {
5147 return;
5148 }
5149
5150 // safari caches class attributes, doesn't catch changes (in 3.2)
5151 div.lastchild.classname = "e";
5152
5153 if ( div.getelementsbyclassname("e").length === 1 ) {
5154 return;
5155 }
5156
5157 expr.order.splice(1, 0, "class");
5158 expr.find.class = function( match, context, isxml ) {
5159 if ( typeof context.getelementsbyclassname !== "undefined" && !isxml ) {
5160 return context.getelementsbyclassname(match[1]);
5161 }
5162 };
5163
5164 // release memory in ie
5165 div = null;
5166 })();
第5140~5155行:测试当前浏览器是否正确支持方法getelementsbyclassname()。测试思路是先构造一段dom结构,然后调用方法getelementsbyclassname(),检查是否返回期望数量的元素。如果不支持或不能正确支持,则不做任何事情。
第5157~5162行:如果当前浏览器支持方法getelementsbyclassname(),则:
向sizzle.selectors.order中插入"class",由["id", "name", "tag"]变为["id", "class", "name", "tag"],插入位置在"id"之后、"name"之前。
向sizzle.selectors.find中插入"class"对应的查找函数。
3.9.2 sizzle.selectors.match/leftmatch
对象sizzle.selectors.match/leftmatch中存放了表达式类型和正则的映射,正则用于确定块表达式的类型,并解析其中的参数。
4225 id: /#((?:[\w\u00c0-\uffff\-]|\\.)+)/,
4226 class: /\.((?:[\w\u00c0-\uffff\-]|\\.)+)/,
4227 name: /\[name=['"]*((?:[\w\u00c0-\uffff\-]|\\.)+)['"]*\]/,
4228 attr: /\[\s*((?:[\w\u00c0-\uffff\-]|\\.)+)\s*(?:(\s?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uffff\-]|\\.)*)|)|)\s*\]/,
4229 tag: /^((?:[\w\u00c0-\uffff\*\-]|\\.)+)/,
4230 child: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|
(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
4232 pseudo: /:((?:[\w\u00c0-\uffff\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]
*)+)\2\))?/
4749 };
4751 var origpos = expr.match.pos,
4752 fescape = function(all, num){
4753 return "\\" + (num - 0 + 1);
4754 };
4755
4756 for ( var type in expr.match ) {
4757 expr.match[ type ] = new regexp( expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
4758 expr.leftmatch[ type ] = new regexp( /(^(?:.|\r|\n)*?)/.source + expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
4759 }
第4224~4233行:定义一组正则,稍后会逐个分析和测试。
第4756~4759行:为对象sizzle.sectors.match中的正则增加一段后缀正则/(?![^\[]*\])(?![^\()*\])/,然后再加上一段前缀正则/(^(?:.|\r|\n)*?)/,来构造对象sizzle.sectors.leftmatch中的同名正则。因为增加的前缀正则中包含了一个分组,所以原正则中的分组编号需要加1后移。
1.?后缀正则/(?![^\[]*\])(?![^\()*\])/
后缀正则/(?![^\[]*\])(?![^\()*\])/要求接下来的字符不能含有"]"、")",用于确保选择器表达式的语法正确,以及确保正确匹配嵌套选择器表达式。
例如,执行$("input[name=foo\\.baz]]")时会抛出语法异常,因为选择器表达式"input
[name=foo\\.baz]]"的末尾多了一个"]",对象sizzle.selectors.match/leftmatch中没有正则可以匹配其中的"[name=foo\\.baz]]";如果没有后缀正则,则sizzle.selectors.match/leftmatch.name会匹配"[name=foo\\.baz]]",并执行查找,而不会抛出语法异常。
又如,执行$("input[name=foo.baz]")时会抛出语法异常,因为选择器表达式"input
[name=foo.baz]"没有转义点号,对象sizzle.selectors.match/leftmatch中没有正则可以匹配其中的"[name=foo.baz]";如果没有后缀正则,则对象sizzle.selectors.match/leftmatch.class会匹配"input[name=foo.baz]"中的".baz",并执行查找,然后用"input[name=foo ]"过滤查找结果,而不会抛出语法异常。
再如,执行$("input:not(.foo)")时,会先查找匹配"input"的元素集合,然后从中过滤不匹配".foo"的元素集合;如果没有后缀正则,则会变为先查找匹配".foo"的元素集合,然后从中过滤匹配"input:not()"的元素集合。
读者可以将第4757行代码注释掉,然后测试和验证上述例子。
2.?前缀正则/(^(?:.|\r|\n)*?)/
前缀正则/(^(?:.|\r|\n)*?)/用于捕获匹配正则的表达式之前的字符,主要是捕获转义反斜杠,以支持将特殊字符作为普通字符使用。
例如,".test\\#id",在用正则sizzle.selectors.match.id匹配时会发现"#"之前是转义反斜杠"\\",这时将认为该表达式的类型不是id;如果没有前缀正则,则会先查找匹配"#id"的元素集合,然后从中过滤出匹配".test\\"的元素集合。
接下来分析和测试对象sizzle.selectors.match中id、class、name、attr、tag、child、pos、pseudo对应的正则。测试用例参考自sizzle的测试用例https://github.com/jquery/sizzle/blob/1.7.1/test/unit/selector.js。
3.?id
正则sizzle.selector.match.id用于匹配简单表达式"#id",并解析"#"之后的字符串,其中含有1个分组:id。解析图见图3-7,测试用例见表3-3。
图3-7 正则sizzle.selectors.match.id
表3-3 正则 sizzle.selectors.match.id
序 号 测 试 用 例 运行结果
1 id.exec("#id") ["#id", "id"]
2 id.exec("#firstp#simon1") ["#firstp", "firstp"]
3 id.exec("#台北ta?ibe?i") ["#台北ta?ibe?i", "台北ta?ibe?i"]
4 id.exec("#foo\\:bar") ["#foo\\:bar", "foo\\:bar"]
5 id.exec("#test\\.foo\\[5\\]bar") ["#test\\.foo\\[5\\]bar", "test\\.foo\\[5\\]bar"]
4.?class
正则 sizzle.selector.match.class 用于匹配简单表达式".class",并解析"."之后的字符串,其中含有1个分组:类样式。解析图见图3-8,测试用例见表3-4。
图3-8 正则sizzle.selectors.match.class
表3-4 正则 sizzle.selectors.match.class
序 号 测 试 用 例 运 行 结 果
1 class.exec(".blog") [".blog", "blog"]
2 class.exec(".blog.link") [".blog", "blog"]
3 class.exec(".台北ta?ibe?i") [".台北ta?ibe?i", "台北ta?ibe?i"]
4 class.exec(".foo\\:bar") [".foo\\:bar", "foo\\:bar"]
5 class.exec(".test\\.foo\\[5\\]bar") [".test\\.foo\\[5\\]bar", "test\\.foo\\[5\\]bar"]
5.?name
正则sizzle.selector.match.name用于匹配属性表达式"[ name = "value" ]",并解析属性name的值,其中含有1个分组:属性name的值。解析图见图3-9,测试用例见表3-5。
图3-9 正则sizzle.selectors.match.name
表3-5 正则 sizzle.selectors.match.name
1 name.exec("input[name=action]") ["[name=action]", "action"]
2 name.exec("input[name='action']") ["[name='action']", "action"]
3 name.exec("input[name=\"action\"]") ["[name="action"]", "action"]
4 name.exec("input[name=\"types[]\"]") null
6.?attr
正则sizzle.selector.match.attr用于匹配属性表达式"[attribute = "value"]",并解析属性名和属性值,其中含有5个分组:属性名、等号部分、引号、属性值、无引号时的属性值。解析图见图3-10,测试用例见表3-6。
7.tag
正则sizzle.selector.match.tag用于匹配简单表达式"tag",并解析标签名,其中含有1个分组:标签名。解析图见图3-11,测试用例见表3-7。
8.?child
正则sizzle.selector.match.child用于匹配子元素伪类表达式:nth-child(index/even/odd/equation)、:first-child、:last-child、:only-child,并解析子元素伪类和伪类参数,其中含有2个分组:子元素伪类、伪类参数。解析图见图3-12,测试用例见表3-8。
9.?pos
正则sizzle.selector.match.pos用于匹配位置伪类表达式":eq(index)"、":gt(index)"、":lt(index)"、":first"、":last"、":odd"、":even",并解析位置伪类和伪类参数,其中含有2个分组:位置伪类、伪类参数。解析图见图3-13,测试用例见表3-9。
10.?pseudo
正则 sizzle.selector.match.pseudo 用于匹配伪类表达式,请解析 ":" 之后的伪类和伪类参数,其中含有 3 个分组:伪类、引号、伪类参数。解析图见图3-14,测试用例见表3-10。
表3-6 正则sizzle.selectors.match.attr
序号 测 试 用 例 运 行 结 果
1 attr.exec("a[title]") ["[title]", "title", undefined, undefined, undefined, undefined]
2 attr.exec("a[title=]") ["[title=]", "title", "=", undefined, undefined, ""]
3 attr.exec("a[rel='bookmark']") ["[rel='bookmark']", "rel", "=", "'", "bookmark", undefined]
4 attr.exec("a[rel=\"bookmark\"]") ["[rel="bookmark"]", "rel", "=", """, "bookmark", undefined]
5 attr.exec("a[rel=bookmark]") ["[rel=bookmark]", "rel", "=", undefined, undefined, "bookmark"]
6 attr.exec("a[rel='bookmark']") ["[rel='bookmark']", "rel", "=", "'", "bookmark", undefined]
7 attr.exec("input[name=foo\\.baz]") ["[name=foo\\.baz]", "name", "=", undefined, undefined, "foo\\.baz"]
8 attr.exec("input[name=foo\\[baz\\]]") ["[name=foo\\[baz\\]]", "name", "=", undefined, undefined, "foo\\
[baz\\]"]
9 attr.exec("a[href='http://www.google.com/']") ["[href='http://www.google.com/']", "href", "=", "'", "http://www.google.com/", undefined]
10 attr.exec("a[href^='http://www']") ["[href^='http://www']", "href", "^=", "'", "http://www", undefined]
11 attr.exec("a[href$='org/']") ["[href$='org/']", "href", "$=", "'", "org/", undefined]
12 attr.exec("a[href*='google']") ["[href*='google']", "href", "*=", "'", "google", undefined]
13 attr.exec("option[value='']") ["[value='']", "value", "=", "'", "", undefined]
14 attr.exec("option[value!='']") ["[value!='']", "value", "!=", "'", "", undefined]
15 attr.exec("[xml\\:test]") ["[xml\\:test]", "xml\\:test", undefined, undefined, undefined, undefined]
16 attr.exec("[data-foo]") ["[data-foo]", "data-foo", undefined, undefined, undefined, undefined]
图3-11 正则sizzle.selectors.match.tag
表3-7 正则 sizzle.selectors.match.tag
1 tag.exec("body") ["body", "body"]
2 tag.exec("html") ["html", "html"]
3 tag.exec("h1") ["h1", "h1"]
表3-8 正则 sizzle.selectors.match.child
1 child.exec("p:first-child") [":first-child", "first", undefined]
2 child.exec("p:only-child") [":only-child", "only", undefined]
3 child.exec("option:nth-child") [":nth-child", "nth", undefined]
4 child.exec("option:nth-child(even)") [":nth-child(even)", "nth", "even"]
5 child.exec("option:nth-child(odd)") [":nth-child(odd)", "nth", "odd"]
6 child.exec("option:nth-child(1)") [":nth-child(1)", "nth", "1"]
7 child.exec("option:nth-child(+1)") [":nth-child(+1)", "nth", "+1"]
8 child.exec("option:nth-child(-1)") [":nth-child(-1)", "nth", "-1"]
9 child.exec("option:nth-child(0n+3)") [":nth-child(0n+3)", "nth", "0n+3"]
10 child.exec("option:nth-child(1n)") [":nth-child(1n)", "nth", "1n"]
11 child.exec("option:nth-child(n)") [":nth-child(n)", "nth", "n"]
12 child.exec("option:nth-child(+n)") [":nth-child(+n)", "nth", "+n"]
13 child.exec("option:nth-child(-1n + 3)") [":nth-child-1n + 3)", "nth", "-1n + 3"]
14 child.exec("option:nth-child(-n+3)") [":nth-child(-n+3)", "nth", "-n+3"]
15 child.exec("option:nth-child(-1n+3)") [":nth-child(-1n+3)", "nth", "-1n+3"]
16 child.exec("option:nth-child(2n)") [":nth-child(2n)", "nth", "2n"]
17 child.exec("option:nth-child(2n + 1)") [":nth-child(2n+1)", "nth", "2n+1"]
18 child.exec("option:nth-child(2n + 1)") [":nth-child(2n + 1)", "nth", "2n + 1"]
19 child.exec("option:nth-child(+2n+1)") [":nth-child(+2n + 1)", "nth", "+2n + 1"]
20 child.exec("option:nth-child(3n)") [":nth-child(3n)", "nth", "3n"]
21 child.exec("option:nth-child(3n+0)") [":nth-child(3n+0)", "nth", "3n+0"]
22 child.exec("option:nth-child(3n+1)") [":nth-child(3n+1)", "nth", "3n+1"]
23 child.exec("option:nth-child(3n-0)") [":nth-child(3n-0)", "nth", "3n-0"]
24 child.exec("option:nth-child(3n-1)") [":nth-child(3n-1)", "nth", "3n-1"]
图3-13 正则sizzle.selectors.match.pos
表3-9 正则 sizzle.selectors.match.pos
1 pos.exec("p:nth(1)") [":nth(1)", "nth", "1"]
2 pos.exec("p:eq(2)") [":eq(2)", "eq", "2"]
3 pos.exec("p:gt(3)") [":gt(3)", "gt", "3"]
4 pos.exec("p:lt(4)") [":lt(4)", "lt", "4"]
5 pos.exec("p:first") [":first", "first", undefined]
6 pos.exec("p:last") [":last", "last", undefined]
7 pos.exec("p:even") [":even", "even", undefined]
8 pos.exec("p:odd") [":odd", "odd", undefined]
图3-14 正则 sizzle.selectors.match.pseudo
表3-10 正则 sizzle.selectors.match.pseudo
1 pseudo.exec("p:has(a)") [":has(a)", "has", "", "a"]
2 pseudo.exec("a:contains(google)") [":contains(google)", "contains", "", "google"]
3 pseudo.exec("input:focus") [":focus", "focus", undefined, undefined]
4 pseudo.exec(":input") [":input", "input", undefined, undefined]
5 pseudo.exec(":radio") [":radio", "radio", undefined, undefined]
6 pseudo.exec(":checkbox") [":checkbox", "checkbox", undefined, undefined]
7 pseudo.exec(":text") [":text", "text", undefined, undefined]
8 pseudo.exec(":radio:checked") [":radio", "radio", undefined, undefined]
9 pseudo.exec(":checkbox:checked") [":checkbox", "checkbox", undefined, undefined]
10 pseudo.exec("option:selected") [":selected", "selected", undefined, undefined]
11 pseudo.exec(":header") [":header", "header", undefined, undefined]
12 pseudo.exec(":empty") [":empty", "empty", undefined, undefined]
13 pseudo.exec(":parent") [":parent", "parent", undefined, undefined]
14 pseudo.exec(":hidden") [":hidden", "hidden", undefined, undefined]
15 pseudo.exec(":visible") [":visible", "visible", undefined, undefined]
3.9.3 sizzle.selectors.find
对象sizzle.selectors.find 中定义了id、class、name、tag所对应的查找函数,称为“查找函数集”。其中,class需要浏览器支持方法getelementsbyclassname()。
查找函数会返回元素集合或 undefined,内部通过调用相应的原生方法来查找元素,如表3-11所示。查找函数调用原生方法前会检查上下文是否支持原生方法。
表3-11 查找函数集 sizzle.selectors.find
序 号 类型 原 生 方 法 说 明
1 id getelementbyid() 查找拥有指定id的第一个元素
2 class getelementsbyclassname() 查获拥有指定类样式的元素集合
3 name getelementsbyname() 查获拥有指定name的元素集合
4 tag getelementsbytagname() 查找拥有指定标签名的元素集合
1.?id
相关代码如下所示:
4340 find: {
4341 id: function( match, context, isxml ) {
4342 if ( typeof context.getelementbyid !== "undefined" && !isxml ) {
4343 var m = context.getelementbyid(match[1]);
4344 // check parentnode to catch when blackberry 4.6 returns
4345 // nodes that are no longer in the document #6963
4346 return m && m.parentnode ? [m] : [];
4347 }
4348 },
4370 },
2.?class
5139 (function(){
// 测试浏览器是否支持方法 getelementsbyclassname()
// 如果不支持,则不做任何事情
// 如果当前浏览器支持方法 getelementsbyclassname()
3.?name
4350 name: function( match, context ) {
4351 if ( typeof context.getelementsbyname !== "undefined" ) {
4352 var ret = [],
4353 results = context.getelementsbyname( match[1] );
4354
4355 for ( var i = 0, l = results.length; i < l; i++ ) {
4356 if ( results[i].getattribute("name") === match[1] ) {
4357 ret.push( results[i] );
4358 }
4359 }
4360
4361 return ret.length === 0 ? null : ret;
4362 }
4363 },
4364
4.?tag
4365 tag: function( match, context ) {
4366 if ( typeof context.getelementsbytagname !== "undefined" ) {
4367 return context.getelementsbytagname( match[1] );
4368 }
4369 }
3.9.4 sizzle.selectors.prefilter
对象sizzle.selectors.prefilter中定义了类型class、id、tag、child、attr、pseudo、pos所对应的预过滤函数,称为“预过滤函数集”。
在方法sizzle.filter( expr, set, inplace, not )中,预过滤函数在过滤函数sizzle.selectors.filter[ type ]之前被调用,见图3-6。调用预过滤函数时的参数格式为:
sizzle.selectors.prefilter[ type ]( 正则匹配结果 match, 元素集合 curloop, 是否缩小元素集合 inplace, 新集合 result, 是否取反 not, isxml )
预过滤函数用于在过滤函数之前修正与过滤操作相关的参数,每种类型的预过滤函数其修正行为如表3-12所示。
表3-12 预过滤函数集 sizzle.selectors.prefilter
序 号 类 型 修 正 行 为 序 号 类 型 修 正 行 为
1 class 过滤不匹配元素,或缩小元素集合 5 attr 修正属性名和属性值
2 id 过滤转义反斜杠 6 pseudo 处理:not( selector )的伪类参数
3 tag 过滤转义反斜杠,转为小写 7 pos 修正位置伪类的参数下标
4 child 格式化子元素伪类参数
预过滤函数有3种返回值,对应的含义如表3-13所示。
表3-13 预过滤函数的返回值
序 号 返 回 值 说 明
1 false 已经执行过滤,或已经缩小候选集,不需要再执行过滤函数,例如,class
2 true 需要继续执行其他的预过滤函数,尚不到执行过滤函数的时候,例如,在pseudo预过滤函数中遇到pos、child时
3 其他 可以调用对应的过滤函数
对象sizzle.selectors.prefilter的总体源码结构如下所示:
var expr = sizzle.selectors = {
prefilter: {
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 ) { ... }
},
};
下面对其中的预过滤函数逐个进行介绍和分析。
1.?class
类样式预过滤函数sizzle.selectors.prefilter.class( match, curloop, inplace, result, not, isxml )负责检查元素集合中的每个元素是否含有指定的类样式。如果参数inplace为true,则将不匹配的元素替换为false;如果参数inplace不是true,则将匹配元素放入元素集合result中,以此来不断地缩小元素集合。关于正则sizzle.selectors.match/leftmatch.class的说明请参见3.9.2节。
3866 rbackslash = /\\/g,
4371 prefilter: {
4372 class: function( match, curloop, inplace, result, not, isxml ) {
4373 match = " " + match[1].replace( rbackslash, "" ) + " ";
4374
4375 if ( isxml ) {
4376 return match;
4377 }
4378
4379 for ( var i = 0, elem; (elem = curloop[i]) != null; i++ ) {
4380 if ( elem ) {
4381 if ( not ^ (elem.classname && (" " + elem.classname +
" "). replace(/[\t\n\r]/g, " ").indexof(match) >= 0) ) {
4382 if ( !inplace ) {
4383 result.push( elem );
4384 }
4385
4386 } else if ( inplace ) {
4387 curloop[i] = false;
4388 }
4389 }
4390 }
4391
4392 return false;
4393 },
4475 },
第4373行:检查类样式的技巧是在前后加空格,然后用字符串方法indexof()进行判断。
第4379~4390行:遍历元素集合curloop,检测每个元素是否含有指定名称的类样式;如果参数not不是true,则保留匹配元素,并排除不匹配元素;如果参数not是true,则保留不匹配元素,排除匹配元素。如果参数inplace为true,则将不匹配的元素替换为false;如果参数inplace不是true,则不修改元素集合curloop,而是将匹配元素放入元素集合result中,以此来不断地缩小元素集合。
第4381~4388行:这段if-else-if语句块的逻辑有些绕,可以这样理解:if代码块表示的是过滤时通过了的情况,else-if语句块表示的是过滤时未通过的情况。
第4392行:class预过滤函数总是返回false,表示已经执行过滤,或已缩小候选集,不需要再执行class过滤函数。
2.?id
id预过滤函数sizzle.selectors.prefilter.id( match )负责过滤转义反斜杠,从匹配结果match中提取并返回id值。关于正则sizzle.selectors.match/leftmatch.id的具体说明请参见3.9.2节。
4395 id: function( match ) {
4396 return match[1].replace( rbackslash, "" );
4397 },
3.?tag
标签预过滤函数sizzle.selectors.prefilter.tag( match, curloop )负责过滤转义反斜杠,转换为小写,从匹配结果match中提取并返回标签名。关于正则sizzle.selectors.match/left
match.tag的具体说明参见3.9.2节。
4399 tag: function( match, curloop ) {
4400 return match[1].replace( rbackslash, "" ).tolowercase();
4401 },
4.?child
子元素伪类预过滤函数sizzle.selectors.prefilter.child( match )负责将伪类:nth-child
( index/even/odd/equation )的参数格式化为first*n + last,例如,将odd格式化为2n+1。关于正则sizzle.selectors.match/leftmatch.child的具体说明请参见3.9.2节。
4403 child: function( match ) {
4404 if ( match[1] === "nth" ) {
4405 if ( !match[2] ) {
4406 sizzle.error( match[0] );
4407 }
4408
4409 match[2] = match[2].replace(/^\+|\s*/g, '');
4410
4411 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
4412 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
4413 match[2] === "even" && "2n" ||
match[2] === "odd" && "2n+1" ||
4414 !/\d/.test( match[2] ) && "0n+" + match[2] ||
match[2]);
4415
4416 // calculate the numbers (first)n+(last) including if they are negative
4417 match[2] = (test[1] + (test[2] || 1)) - 0;
4418 match[3] = test[3] - 0;
4419 }
4420 else if ( match[2] ) {
4421 sizzle.error( match[0] );
4422 }
4423
4424 // todo: move to normal caching system
4425 match[0] = done++;
4426
4427 return match;
4428 },
第4409行:替换伪类开头的加号和包含的空格,例如,:nth-child(+1)→:nth-child(1)、
:nth-child(2n + 1)→:nth-child(2n+1)。
第4412~4414行:将伪类参数统一格式化为first*n + last,例如,even→2n、odd→
2n+1、数字→0n+数字。正则/(-?)(\d*)(?:n([+\-]?\d*))?/含有3个分组:负号、first部分、last部分。
第4417~4418行:计算first部分和last部分。注意减0是为了将字符串强制转换为数值。
第4425行:为本次过滤分配一个唯一的标识,用于优化过滤过程,请参见3.9.7节对子元素伪类过滤函数sizzle.selectors.filter.child( elem, match )的介绍和分析。
5.?attr
属性预过滤函数sizzle.selectors.prefilter.attr( match, curloop, inplace, result, not, isxml )负责修正匹配结果match中的属性名和属性值。关于正则sizzle.selectors.match/leftmatch.attr的具体说明请参见3.9.2节。
4430 attr: function( match, curloop, inplace, result, not, isxml ) {
4431 var name = match[1] = match[1].replace( rbackslash, "" );
4432
4433 if ( !isxml && expr.attrmap[name] ) {
4434 match[1] = expr.attrmap[name];
4435 }
4436
4437 // handle if an un-quoted value was used
4438 match[4] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
4439
4440 if ( match[2] === "~=" ) {
4441 match[4] = " " + match[4] + " ";
4442 }
4443
4444 return match;
4445 },
第4431~4435行:修正属性名。删除转义反斜杠,修正某些特殊属性名。
第4438~4442行:修正属性值。合并分组4和分组5的值,删除转义反斜杠。当属性表达式的属性值有引号时,属性值存储在match[4],否则存储在match[5]。如果等号部分是~=,表示是单词匹配,则在属性值前后加空格;在过滤函数中,对于~=,会在元素的属性值前后加空格,然后用字符串方法indexof()检查。
6.?pseudo
伪类预过滤函数sizzle.selectors.prefilter.pseudo( match, curloop, inplace, result, not )主要负责处理伪类表达式是:not( selector )的情况,该函数会将匹配结果match中的分组3(即伪类参数selector)替换为与之匹配的元素集合;对于位置伪类和子元素伪类,则返回true,继续执行各自对应的预过滤函数。关于正则sizzle.selectors.match/leftmatch.pseudo的具体说明请参见3.9.2节。
4447 pseudo: function( match, curloop, inplace, result, not ) {
4448 if ( match[1] === "not" ) {
4449 // if we're dealing with a complex expression, or a simple one
4450 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
4451 match[3] = sizzle(match[3], null, null, curloop);
4452
4453 } else {
4454 var ret = sizzle.filter(match[3], curloop, inplace, true ^ not);
4455
4456 if ( !inplace ) {
4457 result.push.apply( result, ret );
4458 }
4459
4460 return false;
4461 }
4462
4463 } else if ( expr.match.pos.test( match[0] ) || expr.match.child.test( match[0] ) ) {
4464 return true;
4465 }
4466
4467 return match;
4468 },
第4447行:参数match是正则sizzle.selectors.match.pseudo匹配块表达式的结果,含有3个分组:伪类、引号、伪类参数。
第4448~4461行:如果伪类是:not(selector),则将匹配结果match中的分组3(即伪类参数selector)替换为与之其匹配的元素集合。在对应的过滤函数中,会筛选出不在分组3中的元素。
第4463~4465行:如果是位置伪类pos或子元素伪类child,则返回true,表示仍然需要继续执行各自所对应的预过滤函数。注意,位置伪类pos和子元素伪类child有着自己的预过滤函数。
7.?pos
位置伪类预过滤函数sizzle.selectors.prefilter.pos( match )负责在匹配结果match的头部插入一个新元素true,使得匹配结果match中位置伪类参数的下标变为了3,从而与伪类的匹配结果保持一致。关于正则sizzle.selectors.match/leftmatch.pos/pseudo的具体说明请参见3.9.2节。
4470 pos: function( match ) {
4471 match.unshift( true );
4472
4473 return match;
4474 }
3.9.5 sizzle.selectors.filters
对象sizzle.selectors.filters中定义了一组伪类和对应的伪类过滤函数,称为“伪类过滤函数集”。支持的伪类有::enabled、:disabled、:checked、:selected、:parent、:empty、:has、:header、:text、:radio、:checkbox、:file、:password、:submit、:image、:reset、:button、:input、:focus。方法调用链为:sizzle.filter()→sizzle.selectors.filter.pseudo()→sizzle.selectors.filters,如图3-1所示。
伪类过滤函数负责检查元素是否匹配伪类,返回一个布尔值,其参数格式为:
sizzle.selectors.filters[ 伪类 ]( 元素, 序号, 正则匹配结果, 元素集合 );
// 正则匹配结果是正则 sizzle.selectors.match.pseudo 匹配块选择器表达式的结果,含有 3 个分组:伪类、引号、伪类参数
相关代码如下所示,为了方便解释,代码中增加了示例和注释:
4477 filters: {
// $(':enabled') 匹配所有可用元素(未禁用的,不隐藏的)
4478 enabled: function( elem ) {
4479 return elem.disabled === false && elem.type !== "hidden";
4480 },
4481 // $(':disabled') 匹配所有不可用元素(禁用的)
4482 disabled: function( elem ) {
4483 return elem.disabled === true;
4484 },
4485 // $(':checked') 匹配所有选中的被选中元素,包括复选框、单选按钮,不包括 option 元素
4486 checked: function( elem ) {
4487 return elem.checked === true;
4488 },
4489 // $(':selected') 匹配所有选中的 option 元素
4490 selected: function( elem ) {
4491 // accessing this property makes selected-by-default
4492 // options in safari work properly
4493 if ( elem.parentnode ) {
4494 elem.parentnode.selectedindex;
4495 }
4496
4497 return elem.selected === true;
4498 },
4499 // $(':parent') 匹配所有含有子元素或文本的元素
4500 parent: function( elem ) {
4501 return !!elem.firstchild;
4502 },
4503 // $(':empty') 匹配所有不包含子元素或者文本的空元素
4504 empty: function( elem ) {
4505 return !elem.firstchild;
4506 },
4507 // $(':has(selector)') 匹配含有选择器所匹配元素的元素
4508 has: function( elem, i, match ) {
4509 return !!sizzle( match[3], elem ).length;
4510 },
4511 // $(':header') 匹配如 h1、h2、h3 之类的标题元素
4512 header: function( elem ) {
4513 return (/h\d/i).test( elem.nodename );
4514 },
4515 // $(':text') 匹配所有单行文本框
4516 text: function( elem ) {
4517 var attr = elem.getattribute( "type" ), type = elem.type;
4518 // ie6 and 7 will map elem.type to 'text' for new html5 types (search, etc)
4519 // use getattribute instead to test this case
4520 return elem.nodename.tolowercase() === "input" && "text" === type && ( attr === type || attr === null );
4521 },
4522 // $(':radio') 匹配所有单选按钮
4523 radio: function( elem ) {
4524 return elem.nodename.tolowercase() === "input" && "radio" === elem.type;
4525 },
4526 // $(':checkbox') 匹配所有复选框
4527 checkbox: function( elem ) {
4528 return elem.nodename.tolowercase() === "input" && "checkbox" === elem.type;
4529 },
4530 // $(':file') 匹配所有文件域
4531 file: function( elem ) {
4532 return elem.nodename.tolowercase() === "input" && "file" === elem.type;
4533 },
4534 // $(':password') 匹配所有密码框
4535 password: function( elem ) {
4536 return elem.nodename.tolowercase() === "input" && "password" === elem.type;
4537 },
4538 // $(':submit') 匹配所有提交按钮
4539 submit: function( elem ) {
4540 var name = elem.nodename.tolowercase();
4541 return (name === "input" || name === "button") && "submit" === elem.type;
4542 },
4543 // $(':image') 匹配所有图像域
4544 image: function( elem ) {
4545 return elem.nodename.tolowercase() === "input" && "image" === elem.type;
4546 },
4547 // $(':reset') 匹配所有重置按钮
4548 reset: function( elem ) {
4549 var name = elem.nodename.tolowercase();
4550 return (name === "input" || name === "button") && "reset" === elem.type;
4551 },
4552 // $(':button') 匹配所有按钮
4553 button: function( elem ) {
4554 var name = elem.nodename.tolowercase();
4555 return name === "input" && "button" === elem.type || name === "button";
4556 },
4557 // $(':input') 匹配所有 input、textarea、select、button 元素
4558 input: function( elem ) {
4559 return (/input|select|textarea|button/i).test( elem.nodename );
4560 },
4561 // $(':focus') 匹配当前焦点元素
4562 focus: function( elem ) {
4563 return elem === elem.ownerdocument.activeelement;
4564 }
4565 },
3.9.6 sizzle.selectors.setfilters
对象sizzle.selectors.setfilters中定义了一组位置伪类和对应的伪类过滤函数,称为“位置伪类过滤函数集”。支持的位置伪类有::first、:last、:even、:odd、:lt(index)、:gt(index)、:nth(index)、
:eq(index)。方法调用链为:sizzle.filter()→sizzle.selectors.filter.pos()→sizzle.selectors.setfilters,如图3-1所示。
位置伪类过滤函数通过比较下标来确定元素在集合中的位置,返回一个布尔值,其参数格式为:
sizzle.selectors.setfilters[ 位置伪类 ]( 元素, 下标, 正则匹配结果, 元素集合 );
// 正则匹配结果是正则 sizzle.selectors.match.pos 匹配块选择器表达式的结果,含有 2 个分组:位置伪类、位置伪类参数
4566 setfilters: {
// $(':first') 匹配找到的第一个元素
4567 first: function( elem, i ) {
4568 return i === 0;
4569 },
4570 // $(':last') 匹配找到的最后一个元素
4571 last: function( elem, i, match, array ) {
4572 return i === array.length - 1;
4573 },
4574 // $(':even') 匹配所有下标为偶数的元素,从0开始计数
4575 even: function( elem, i ) {
4576 return i % 2 === 0;
4577 },
4578 // $(':odd') 匹配所有下标为奇数的元素,从0开始计数
4579 odd: function( elem, i ) {
4580 return i % 2 === 1;
4581 },
4582 // $(':lt(index)') 匹配所有小于指定下标的元素
4583 lt: function( elem, i, match ) {
4584 return i < match[3] - 0;
4585 },
4586 // $(':gt(index)') 匹配所有大于指定下标的元素
4587 gt: function( elem, i, match ) {
4588 return i > match[3] - 0;
4589 },
4590 // $(':nth(index)') 匹配一个指定下标的元素,从 0 开始计数
4591 nth: function( elem, i, match ) {
4592 return match[3] - 0 === i;
4593 },
4594 // $(':eq(index)') 匹配一个指定下标的元素,从 0 开始计数
4595 eq: function( elem, i, match ) {
4596 return match[3] - 0 === i;
4597 }
4598 },
3.9.7 sizzle.selectors.filter
对象sizzle.selectors.filter中定义了类型pseudo、child、id、tag、class、attr、pos所对应的过滤函数,称为“过滤函数集”。方法调用链为:sizzle.filter()→sizzle.selectors.filter[ type ],如图3-1和图3-6所示。
过滤函数负责检查元素是否匹配过滤表达式,返回一个布尔值,其参数格式为:
sizzle.selectors.filter[ 类型 ]( 元素, 正则匹配结果或过滤表达式, 下标, 元素集合 )
// 正则匹配结果指 sizzle.selectors.match 中对应的正则匹配块表达式的结果
// 过滤表达式指经过 sizzle.selectors.prefilter 处理后的块表达式
sizzle.selectors.filter的总体源码结构如下所示:
filter: {
pseudo: function( elem, match, i, array ) { ... },
child: function( elem, match ) { ... },
id: function( elem, match ) { ... },
tag: function( elem, match ) { ... },
class: function( elem, match ) { ... },
attr: function( elem, match ) { ... },
pos: function( elem, match, i, array ) { ... }
};
下面对其中的过滤函数逐个进行介绍和分析。
1.?pseudo
伪类过滤函数sizzle.selectors.filter.pseudo( elem, match, i, array )用于检查元素是否匹配伪类。大部分检查通过调用伪类过滤函数集sizzle.selectors.filters中对应的伪类过滤函数来实现,对于伪类:contains(text)、:not(selector)则做特殊处理。具体请参见3.9.5节。
4599 filter: {
4600 pseudo: function( elem, match, i, array ) {
4601 var name = match[1],
4602 filter = expr.filters[ name ];
4603
4604 if ( filter ) {
4605 return filter( elem, i, match, array );
4606
4607 } else if ( name === "contains" ) {
4608 return (elem.textcontent || elem.innertext || gettext([ elem ]) || "").indexof(match[3]) > = 0;
4609
4610 } else if ( name === "not" ) {
4611 var not = match[3];
4612
4613 for ( var j = 0, l = not.length; j < l; j++ ) {
4614 if ( not[j] === elem ) {
4615 return false;
4616 }
4617 }
4618
4619 return true;
4620
4621 } else {
4622 sizzle.error( name );
4623 }
4624 },
4748 }
第4600行:参数match是正则sizzle.selectors.match.pseudo匹配块表达式的结果,含有3个分组:伪类、引号、伪类参数。
第4602~4605行:如果在伪类过滤函数集sizzle.selectors.filters中存在对应的伪类过滤函数,则调用它来检查元素是否匹配伪类。
第4607~4608行:伪类:contains(text)用于匹配包含指定文本的所有元素。如果伪类是:contains( text ),则先取出当前元素的文本内容,然后调用字符串方法indexof()检查是否含有指定的文本。
第4610~4619行:伪类:not(selector)用于匹配与指定选择器不匹配的所有元素。如果伪类是:not(selector),则检查当前元素是否与match[3]中的某个元素相等,如果相等则返回false,否则返回true。在预过滤函数sizzle.selectors.prefilter.pseudo中,对于伪类:not
(selector),会将match[3]替换为其匹配的元素集合。
第4621~4623行:对于不支持的伪类,一律调用方法sizzle.error( msg )抛出语法错误。方法sizzle.error( msg )请参见3.10.4节。
2.?child
子元素伪类过滤函数sizzle.selectors.filter.child( elem, match )用于检查元素是否匹配子元素伪类。支持的子元素伪类如表3-14所示。
表3-14 子元素伪类
序 号 子元素伪类 说 明
1 :nth-child(index/even/odd/equation) 匹配父元素下的第n个子元素或奇偶元素
2 :first-child 匹配父元素的第一个子元素
3 :last-child 匹配父元素的最后一个子元素
4 :only-child 如果某个元素是父元素的唯一子元素,则匹配;如果父元素还含有多个子元素,则不匹配
4626 child: function( elem, match ) {
4627 var first, last,
4628 donename, parent, cache,
4629 count, diff,
4630 type = match[1],
4631 node = elem;
4632
4633 switch ( type ) {
4634 case "only":
4635 case "first":
4636 while ( (node = node.previoussibling) ) {
4637 if ( node.nodetype === 1 ) {
4638 return false;
4639 }
4640 }
4641
4642 if ( type === "first" ) {
4643 return true;
4644 }
4645
4646 node = elem;
4647
4648 case "last":
4649 while ( (node = node.nextsibling) ) {
4650 if ( node.nodetype === 1 ) {
4651 return false;
4652 }
4653 }
4654
4655 return true;
4656
4657 case "nth":
4658 first = match[2];
4659 last = match[3];
4660
4661 if ( first === 1 && last === 0 ) {
4662 return true;
4663 }
4664
4665 donename = match[0];
4666 parent = elem.parentnode;
4667
4668 if ( parent && (parent[ expando ] !== donename || !elem.nodeindex) ) {
4669 count = 0;
4670
4671 for ( node = parent.firstchild; node; node = node.nextsibling ) {
4672 if ( node.nodetype === 1 ) {
4673 node.nodeindex = ++count;
4674 }
4675 }
4676
4677 parent[ expando ] = donename;
4678 }
4679
4680 diff = elem.nodeindex - last;
4681
4682 if ( first === 0 ) {
4683 return diff === 0;
4684
4685 } else {
4686 return ( diff % first === 0 && diff / first >= 0 );
4687 }
4688 }
4689 },
第4634~4655行:如果伪类是:only-child,则检查当前元素之前(previoussibling)和之后(nextsibling)是否有兄弟元素,如果都没有则返回true,否则返回false。注意这里的分支only是通过分支first和分支last实现的。
第4635~4644行:如果伪类是:first-child,则检查当前元素之前(previoussibling)是否有兄弟元素,有则返回false,没有则返回true。
第4648~4655行:如果伪类是:last-child,则检查当前元素之后(nextsibling)是否有兄弟元素,有则返回false,没有则返回true。
第4657~4687行:如果伪类是:nth-child(index/even/odd/equation),则检查当前元素的下标是否匹配伪类参数,检测公式为:
( 当前元素在其父元素中的下标位置 - last ) % first === 0
在预过滤函数sizzle.selectors.prefilter.child中已将伪类参数统一格式化为first*n+last,例如,odd格式化为2n+1,其中,first存储在match[2]中,last存储在match[3]中。
第4665~4678行:找到当前元素的父元素,然后为每个子元素设置属性nodeindex,从而标识出每个子元素的下标位置。如果父元素未被本次过滤标识过,或当前元素未被标识过,才会为子元素设置属性nodeindex,以确保只会标识一次。
match[0]是本次过滤的唯一标识,在执行子元素预过滤函数sizzle.selectors.prefilter.child( match )时被分配,具体请参见3.9.4节。
id过滤函数sizzle.selectors.filter.id( elem, match )用于检查元素的属性id是否与指定的id相等。
4691 id: function( elem, match ) {
4692 return elem.nodetype === 1 && elem.getattribute("id") === match;
4693 },
标签过滤函数sizzle.selectors.filter.tag( elem, match )用于检查元素的标签名nodename是否与指定的标签名相等。
5.?class
类样式过滤函数sizzle.selectors.filter.class( elem, match )用于检查元素的类样式classname是否含有指定的类样式。检查技巧是在类样式前后加空格,然后判断字符串方法indexof()的返回值。
4699 class: function( elem, match ) {
4700 return (" " + (elem.classname || elem.getattribute("class")) + " ")
4701 .indexof( match ) > -1;
4702 },
6.attr
属性过滤函数sizzle.selectors.filter.attr( elem, match )用于检查元素的属性是否匹配属性表达式。支持的属性表达式如表3-15所示。
表3-15 属性表达式
序 号 属性表达式 说 明
1 [attribute] 匹配含有指定属性的元素
2 [attribute=value] 匹配含有指定属性,并且当前属性值等于指定值的元素
3 [attribute!=value] 匹配不包含指定属性,或者当前属性值不等于指定值的元素
4 [attribute^=value] 匹配含有指定属性,并且属性值以指定值开始的元素
5 [attribute$=value] 匹配含有指定属性,并且当前属性值以指定值结束的元素
6 [attribute*=value] 匹配含有指定属性,并且当前属性值包含指定值的元素
7 [attribute|="value"] 匹配含有指定属性,并且当前属性值等于指定值,或者当前属性值以指定值开头,并且后跟一个连字符(-)的元素
8 [attribute~="value"] 匹配含有指定属性,并且当前属性值含有指定单词的元素。单词之间用空格分隔
4704 attr: function( elem, match ) {
4705 var name = match[1],
4706 result = sizzle.attr ?
4707 sizzle.attr( elem, name ) :
4708 expr.attrhandle[ name ] ?
4709 expr.attrhandle[ name ]( elem ) :
4710 elem[ name ] != null ?
4711 elem[ name ] :
4712 elem.getattribute( name ),
4713 value = result + "",
4714 type = match[2],
4715 check = match[4];
4716
4717 return result == null ?
4718 type === "!=" :
4719 !type && sizzle.attr ?
4720 result != null :
4721 type === "=" ?
4722 value === check :
4723 type === "*=" ?
4724 value.indexof(check) >= 0 :
4725 type === "~=" ?
4726 (" " + value + " ").indexof(check) >= 0 :
4727 !check ?
4728 value && result !== false :
4729 type === "!=" ?
4730 value !== check :
4731 type === "^=" ?
4732 value.indexof(check) === 0 :
4733 type === "$=" ?
4734 value.substr(value.length - check.length) === check :
4735 type === "|=" ?
4736 value === check || value.substr(0, check.length + 1) === check + "-" :
4737 false;
4738 },
第4705行:变量name是指定的属性名。
第4706~4712行:变量result是元素的html属性值或dom属性值。在jquery中,因为sizzle.attr()等价于jquery.attr(),因此总是返回html属性,所以变量result也总是html属性值;在独立使用sizzle时,则是先尝试读取dom属性值,如果不存在才会读取html属性值。
第4713~4715行:变量value是变量result字符串格式;变量type是属性表达式的等号部分,例如,=、!=;变量check是指定的属性值。
第4717~4737行:根据等号部分,采用不同的比较方式来检查元素是否匹配属性表达式。由于这段复合三元表达式太长太复杂,因此,下面将格式稍做调整并加上注释,以便于阅读理解:
// [name!=value] 不包含指定属性
return result == null ? type === "!=" :
// [name] 包含指定属性
!type && sizzle.attr ? result != null :
// [name=check] 包含指定属性,属性值等于指定值
type === "=" ? value === check :
// [name*=check] 含有指定属性,属性值包含指定值
type === "*=" ? value.indexof(check) >= 0 :
// [name~="value"] 含有指定属性,属性值含有指定单词
type === "~=" ? (" " + value + " ").indexof(check) >= 0 :
// 如果没有指定值 check,只有指定属性值,并且属性值不是 false,才会返回 true
!check ? value && result !== false :
// 以下均有指定值 check
// [name!=check] 含有指定属性,属性值不等于指定值
type === "!=" ? value !== check :
// [name^=check] 含有指定属性,属性值以指定值开始
type === "^=" ? value.indexof(check) === 0 :
// [name$=check] 含有指定属性,属性值以指定值结束
type === "$=" ? value.substr(value.length - check.length) === check :
// [name|=check] 含有指定属性,属性值等于指定值,或者以指定值开头,且后跟一个连字符(-)
type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" :
false;
位置伪类过滤函数sizzle.selectors.filter.pos( elem, match, i, array )用于检查元素是否匹配位置伪类,该函数通过调用位置伪类过滤函数集sizzle.selectors.setfilters中对应的位置伪类过滤函数来实现,具体请参见3.9.6节。调用关系如图3-1所示。
4740 pos: function( elem, match, i, array ) {
4741 var name = match[2],
4742 filter = expr.setfilters[ name ];
4743
4744 if ( filter ) {
4745 return filter( elem, i, match, array );
4746 }
4747 }
4748 }
3.10 工具方法
3.10.1 sizzle.uniquesort( results )
工具方法sizzle.uniquesort( results )负责对元素集合中的元素按照出现在文档中的顺序进行排序,并删除重复元素。
4026 sizzle.uniquesort = function( results ) {
4027 if ( sortorder ) {
4028 hasduplicate = basehasduplicate;
4029 results.sort( sortorder );
4030
4031 if ( hasduplicate ) {
4032 for ( var i = 1; i < results.length; i++ ) {
4033 if ( results[i] === results[ i - 1 ] ) {
4034 results.splice( i--, 1 );
4035 }
4036 }
4037 }
4038 }
4039
4040 return results;
4041 };
第4029行:调用数组方法sort()对数组中的元素进行排序。其中,sortorder( a, b )是比较函数,负责比较元素a和元素b在文档中的位置。如果比较函数sortorder( a, b )遇到相等的元素,即重复元素,会设置变量hasduplicate为true。关于比较函数sortorder( a, b )的具体说明请参见3.10.2节。
第4031~4037行:如果变量hasduplicate为true,表示存在重复元素,则遍历数组results,比较相邻元素是否相等,如果相等则删除。
第4028行:开始排序和去重时,先设置变量hasduplicate的默认值为变量basehas
duplicate,变量basehasduplicate指示了javascript引擎在排序时是否会进行优化。
3864 hasduplicate = false,
3865 basehasduplicate = true,
3870 // here we check if the javascript engine is using some sort of
3871 // optimization where it does not always call our comparision
3872 // function. if that is the case, discard the hasduplicate value.
3873 // thus far that includes google chrome.
3874 [0, 0].sort(function() {
3875 basehasduplicate = false;
3876 return 0;
3877 });
第3874~3877行:检查javascript引擎在排序时是否会进行优化。在早期的chrome浏览器中,排序时如果遇到相等的元素,不会调用比较函数,新版本中已经取消了这一优化。如果遇到相等元素便不调用比较函数,此时变量basehasduplicate默认为true,即只能假设数组中含有重复元素;如果遇到相等元素时仍然会调用比较函数,则变量basehasduplicate将被设置为false,这种情况下需要在比较函数中判断是否含有重复元素。
读者可以访问http://bugs.jquery.com/ticket/5380查看该bug的描述。
3.10.2 sortorder( a, b )
函数sortorder( a, b )负责比较元素a和元素b在文档中的位置。如果元素a在元素b之前,则返回-1;如果元素a在元素b之后,则返回1;如果元素a与元素b相等,则返回0。
函数sortorder( a, b )通过调用原生方法comparedocumentposition()或比较原生属性source
index来实现。原生方法comparedocumentposition()用于比较两个元素的文档位置;原生属性sourceindex则返回元素在文档中的序号,返回值等于该元素在document.getelementsby
tagname('*')返回的数组中的下标。更多信息请访问http://www.quirksmode.org/dom/w3c_core.html。
函数sortorder( a, b )执行的3个关键步骤如下:
1)如果浏览器支持原生方法comparedocumentposition(),则调用该方法比较元素位置。
2)如果浏览器支持原生属性sourceindex,则用该属性比较元素位置。
3)否则比较祖先元素的文档位置。
1.?浏览器支持原生方法comparedocumentposition()的情况
如果浏览器支持原生方法comparedocumentposition(),则调用该方法比较元素位置。相关代码如下所示:
4805 var sortorder, siblingcheck;
4806
4807 if ( document.documentelement.comparedocumentposition ) {
4808 sortorder = function( a, b ) {
4809 if ( a === b ) {
4810 hasduplicate = true;
4811 return 0;
4812 }
4813
4814 if ( !a.comparedocumentposition || !b.comparedocumentposition ) {
4815 return a.comparedocumentposition ? -1 : 1;
4816 }
4817
4818 return a.comparedocumentposition(b) & 4 ? -1 : 1;
4819 };
4820
2.?浏览器支持原生属性sourceindex的情况
如果浏览器支持原生属性sourceindex,则用该属性比较元素位置。
4821 } else {
4822 sortorder = function( a, b ) {
4823 // the nodes are identical, we can exit early
4824 if ( a === b ) {
4825 hasduplicate = true;
4826 return 0;
4827
4828 // fallback to using sourceindex (in ie) if it's available on both nodes
4829 } else if ( a.sourceindex && b.sourceindex ) {
4830 return a.sourceindex - b.sourceindex;
4831 }
4832
3.?否则比较祖先元素的文档位置
(1)元素a和元素b是兄弟元素的情况
4833 var al, bl,
4834 ap = [],
4835 bp = [],
4836 aup = a.parentnode,
4837 bup = b.parentnode,
4838 cur = aup;
4839
4840 // if the nodes are siblings (or identical) we can do a quick check
4841 if ( aup === bup ) {
4842 return siblingcheck( a, b );
4843
第4836~4837行、第4841~4842行:变量aup是元素a的父元素,变量bup是元素b的父元素。如果变量aup与变量bup相等,说明元素a和元素b是兄弟元素,则调用函数siblingcheck()比较元素a和元素b的文档位置。函数siblingcheck( a, b, ret )负责比较兄弟元素的文档位置,稍后会看到该函数的源码实现和分析。
(2)没有找到父元素的情况
4844 // if no parents were found then the nodes are disconnected
4845 } else if ( !aup ) {
4846 return -1;
4847
4848 } else if ( !bup ) {
4849 return 1;
4850 }
4851
第4845~4850行:如果元素a没有父元素,则认为元素a不在文档中,返回-1,元素a将排在元素b之前;如果元素b没有父元素,则认为元素b不在文档中,返回1,元素b将排在元素a之前。
(3)查找元素a和元素b的祖先元素
4852 // otherwise they're somewhere else in the tree so we need
4853 // to build up a full list of the parentnodes for comparison
4854 while ( cur ) {
4855 ap.unshift( cur );
4856 cur = cur.parentnode;
4857 }
4858
4859 cur = bup;
4860
4861 while ( cur ) {
4862 bp.unshift( cur );
4863 cur = cur.parentnode;
4864 }
4865
(4)比较祖先元素的文档位置
4866 al = ap.length;
4867 bl = bp.length;
4868
4869 // start walking down the tree looking for a discrepancy
4870 for ( var i = 0; i < al && i < bl; i++ ) {
4871 if ( ap[i] !== bp[i] ) {
4872 return siblingcheck( ap[i], bp[i] );
4873 }
4874 }
4875
第4866~4874行:从最顶层的祖先元素开始向下遍历,如果祖先元素ap[i]与bp[i]不是同一个元素,那么它们必然是兄弟元素,此时可以通过比较两个祖先元素的文档位置,来确定元素a和元素b的相对位置。
(5)元素a和元素b的文档深度不一致的情况
4876 // we ended someplace up the tree so do a sibling check
4877 return i === al ?
4878 siblingcheck( a, bp[i], -1 ) :
4879 siblingcheck( ap[i], b, 1 );
4880 };
4881
第4877~4878行:如果元素a的文档深度较小,此时元素a与元素b的祖先元素bp[i]要么是兄弟元素,要么是同一个元素,可以调用函数siblingcheck( a, b, ret )比较文档位置。
第4877~4879行:如果元素b的文档深度较小,此时元素a的祖先元素ap[i]与元素b要么是兄弟元素,要么是同一个元素,可以调用函数siblingcheck( a, b, ret )比较文档位置。
(6)siblingcheck( a, b, ret )
函数siblingcheck( a, b, ret )负责比较兄弟元素的文档位置。该函数从元素a向后遍历(nextsibling),如果遇到元素b,说明元素a在元素b之前,则返回-1;如果一直没遇到,说明元素a在元素b之后,则返回1。
4882 siblingcheck = function( a, b, ret ) {
4883 if ( a === b ) {
4884 return ret;
4885 }
4886
4887 var cur = a.nextsibling;
4888
4889 while ( cur ) {
4890 if ( cur === b ) {
4891 return -1;
4892 }
4893
4894 cur = cur.nextsibling;
4895 }
4896
4897 return 1;
4898 };
4899 }
3.10.3 sizzle.contains( a, b )
工具方法sizzle.contains( a, b )负责检测元素a是否包含元素b。该方法通过调用原生方法contains()或comparedocumentposition()实现。原生方法contains()用于检测一个元素是否包含另一个元素;原生方法comparedocumentposition()用于比较两个元素的文档位置,更多信息请访问以下网址:
http://ejohn.org/blog/comparing-document-position/
http://www.quirksmode.org/dom/w3c_core.html#miscellaneous
5242 if ( document.documentelement.contains ) {
5243 sizzle.contains = function( a, b ) {
5244 return a !== b && (a.contains ? a.contains(b) : true);
5245 };
5246
5247 } else if ( document.documentelement.comparedocumentposition ) {
5248 sizzle.contains = function( a, b ) {
5249 return !!(a.comparedocumentposition(b) & 16);
5250 };
5251
5252 } else {
5253 sizzle.contains = function() {
5254 return false;
5255 };
5256 }
3.10.4 sizzle.error( msg )
工具方法sizzle.error( msg )用于抛出一个含有选择器表达式语法错误信息的异常。
4178 sizzle.error = function( msg ) {
4180 };
3.10.5 sizzle.gettext( elem )
工具方法sizzle.gettext( elem )用于获取元素集合中所有元素合并后的文本内容。
4182 /**
4183 * utility function for retreiving the text value of an array of dom nodes
4184 * @param {array|element} elem
4185 */
4186 var gettext = sizzle.gettext = function( elem ) {
4187 var i, node,
4188 nodetype = elem.nodetype,
4189 ret = "";
4190
4191 if ( nodetype ) {
4192 if ( nodetype === 1 || nodetype === 9 ) {
4193 // use textcontent || innertext for elements
4194 if ( typeof elem.textcontent === 'string' ) {
4195 return elem.textcontent;
4196 } else if ( typeof elem.innertext === 'string' ) {
4197 // replace ie's carriage returns
4198 return elem.innertext.replace( rreturn, '' );
4199 } else {
4200 // traverse it's children
4201 for ( elem = elem.firstchild; elem; elem = elem.nextsibling) {
4202 ret += gettext( elem );
4203 }
4204 }
4205 } else if ( nodetype === 3 || nodetype === 4 ) {
4206 return elem.nodevalue;
4207 }
4208 } else {
4209
4210 // if no nodetype, this is expected to be an array
4211 for ( i = 0; (node = elem[i]); i++ ) {
4212 // do not traverse comment nodes
4213 if ( node.nodetype !== 8 ) {
4214 ret += gettext( node );
4215 }
4216 }
4217 }
4218 return ret;
4219 };
第4191~4207行:如果参数elem是元素,则尝试读取属性textcontent或innertext,如果不支持则遍历子元素,递归调用工具函数gettext( elem )来获取每个子元素的文本内容,并合并;如果参数elem是text节点或cdatasection节点,则直接返回节点值nodevalue。
第4208~4217行:否则认为参数elem是元素集合,遍历该元素集合,递归调用函数gettext(elem)获取每个元素的文本内容,并合并。
第4218行:最后返回合并后的文本内容。
3.11 便捷方法
3.11.1 sizzle.matches( expr, set )
便捷方法sizzle.matches( expr, set )使用指定的选择器表达式expr对元素集合set进行过滤,并返回过滤结果。
该方法通过简单地调用函数sizzle( selector, context, results, seed )来实现,调用时会将元素集合set作为参数seed传入。
4043 sizzle.matches = function( expr, set ) {
4044 return sizzle( expr, null, null, set );
4045 };
3.11.2 sizzle.matchesselector( node, expr )
便捷方法sizzle.matchesselector( node, expr )用于检查某个元素node是否匹配选择器表达式expr。
如果浏览器支持原生方法matchesselector()、mozmatchesselector()、webkitmatchesselector()、msmatchesselector()中的一种,则尝试调用原生方法检查元素与选择器表达式是否匹配;如果浏览器不支持原生方法,或者支持但是检查失败(抛出异常),则调用函数sizzle( selector, context, results, seed ),检查其返回值的长度是否大于0,调用时会将元素node封装成数组作为参数seed传入。
4047 sizzle.matchesselector = function( node, expr ) {
4048 return sizzle( expr, null, null, [node] ).length > 0;
4049 };
5095 (function(){
5096 var html = document.documentelement,
5097 matches = html.matchesselector || html.mozmatchesselector || html.webkitmatchesselector || html.msmatchesselector;
5098
5099 if ( matches ) {
5100 // check to see if it's possible to do matchesselector
5101 // on a disconnected node (ie 9 fails this)
5102 var disconnectedmatch = !matches.call( document.createelement( "div" ), "div" ),
5103 pseudoworks = false;
5104
5105 try {
5106 // this should fail with an exception
5107 // gecko does not error, returns false instead
5108 matches.call( document.documentelement, "[test!='']:sizzle" );
5109
5110 } catch( pseudoerror ) {
5111 pseudoworks = true;
5112 }
5113
5114 sizzle.matchesselector = function( node, expr ) {
5115 // make sure that attribute selectors are quoted
5116 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
5117
5118 if ( !sizzle.isxml( node ) ) {
5119 try {
5120 if ( pseudoworks || !expr.match.pseudo.test( expr ) && !/!=/.test( expr ) ) {
5121 var ret = matches.call( node, expr );
5122
5123 // ie 9's matchesselector returns false on disconnected nodes
5124 if ( ret || !disconnectedmatch ||
5125 // as well, disconnected nodes are said to be in a document
5126 // fragment in ie 9, so check for that
5127 node.document && node.document.nodetype !== 11 ) {
5128 return ret;
5129 }
5130 }
5131 } catch(e) {}
5132 }
5133
5134 return sizzle(expr, null, null, [node]).length > 0;
5135 };
5136 }
5137 })();
3.12 jquery扩展
3.12.1 暴露sizzle给jquery
下面的代码将sizzle的方法和属性暴露给了jquery:
5288 // expose
5289 // override sizzle attribute retrieval
5290 sizzle.attr = jquery.attr;
5291 sizzle.selectors.attrmap = {};
5292 jquery.find = sizzle;
5293 jquery.expr = sizzle.selectors;
5294 jquery.expr[":"] = jquery.expr.filters;
5295 jquery.unique = sizzle.uniquesort;
5296 jquery.text = sizzle.gettext;
5297 jquery.isxmldoc = sizzle.isxml;
5298 jquery.contains = sizzle.contains;
3.12.2 .find( selector )
方法.find( selector )用于获取匹配元素集合中的每个元素的后代元素,可以用一个选择器表达式、jquery对象或dom元素来过滤查找结果,并用匹配的元素构造一个新的jquery对象作为返回值。该方法通过调用函数 sizzle( selector, context, results, seed )来实现,并在后者的基础上增加了对多上下文和链式语法的支持。
方法.find( selector )执行的2个关键步骤如下:
1)如果参数select是jquery对象或dom元素,则检查其是否是当前元素集合中某个元素的后代元素,是则保留,不是则丢弃。
2)如果参数selector是字符串,则遍历当前元素集合,以每个元素为上下文,调用方法jquery.find( selector, context, results, seed )也就是sizzle( selector, context, results, seed ),查找匹配的后代元素,并将查找结果合并、去重。
5319 jquery.fn.extend({
5320 find: function( selector ) {
5321 var self = this,
5322 i, l;
5323
5324 if ( typeof selector !== "string" ) {
5325 return jquery( selector ).filter(function() {
5326 for ( i = 0, l = self.length; i < l; i++ ) {
5327 if ( jquery.contains( self[ i ], this ) ) {
5328 return true;
5329 }
5330 }
5331 });
5332 }
5333
5334 var ret = this.pushstack( "", "find", selector ),
5335 length, n, r;
5336
5337 for ( i = 0, l = this.length; i < l; i++ ) {
5338 length = ret.length;
5339 jquery.find( selector, this[i], ret );
5340
5341 if ( i > 0 ) {
5342 // make sure that the results are unique
5343 for ( n = length; n < ret.length; n++ ) {
5344 for ( r = 0; r < length; r++ ) {
5345 if ( ret[r] === ret[n] ) {
5346 ret.splice(n--, 1);
5347 break;
5348 }
5349 }
5350 }
5351 }
5352 }
5353
5354 return ret;
5355 },
5470 });
第5320行:定义方法.find( selector ),参数selector可以是选择器表达式,也可以是jquery对象或dom元素。
第5324~5332行:如果参数selector不是字符串,则认为该参数是jquery对象或dom元素,此时先将该参数统一封装为一个新jquery对象,然后遍历新jquery对象,检查其中的元素是否是当前jquery对象中某个元素的后代元素,如果是则保留,不是则丢弃。最后返回含有新jquery对象子集的另一个新jquery对象。
第5334行:调用方法.pushstack( elements, name, arguments )构造一个新的空jquery对象,并将其作为返回值,后面找到的元素都将被添加到该jquery对象中。
第5337~5352行:如果参数selector是字符串,则遍历当前元素集合,以每个元素为上下文,调用方法jquery.find( selector, context, results, seed )也就是sizzle( selector, context, results, seed ),查找匹配的后代元素,并将查找结果合并、去重。
第5341~5351行:从当前元素集合的第2个元素开始,遍历新找到的元素数组,移除其中与已找到的元素相等的元素,以确保找到的元素是唯一的。变量length表示已找到的元素集合的长度,也就是新找到的元素被插入的开始位置;移除时通过将循环变量n自减1,确保接下来的遍历不会漏掉元素。
3.12.3 .has( target )
方法.has( target )用当前jquery对象的子集构造一个新jquery对象,其中只保留子元素可以匹配参数target的元素。参数target可以是选择器表达式、jquery对象或dom元素。该方法通过调用方法.filter( selector )遍历当前匹配元素集合,通过调用jquery.contains( a, b ),也就是sizzle.contains( a, b ),检查匹配元素是否包含了可以匹配参数target的子元素。
5357 has: function( target ) {
5358 var targets = jquery( target );
5359 return this.filter(function() {
5360 for ( var i = 0, l = targets.length; i < l; i++ ) {
5361 if ( jquery.contains( this, targets[i] ) ) {
5362 return true;
5363 }
5364 }
5365 });
5366 },
第5357行:定义方法.has( target ),参数target可以是选择器表达式、jquery对象及元素。
第5358行:构造匹配参数target的jquery对象。
第5359~5365行:调用方法.filter( selector )遍历当前匹配元素集合,检查每个匹配元素是否包含了参数target所匹配的某个元素,如果包含则保留,不包含则丢弃。
3.12.4 .not( selector )、.filter( selector )
方法.not( selector )用当前jquery对象的子集构造一个新jquery对象,其中只保留与参数selector不匹配的元素。参数selector可以是选择器表达式、jquery对象、函数、dom元素或dom元素数组。
方法.filter( selector )用当前jquery对象的子集构造一个新jquery对象,其中只保留与参数selector匹配的元素。参数selector可以是选择器表达式、jquery对象、函数、dom元素或dom元素数组。
方法.not( selector )和.filter( selector )通过调用函数winnow( elements, qualifier, keep )对当前匹配元素集合进行过滤,并用其返回值构造一个新jquery对象。不过,这两个方法的过滤行为正好相反,这种差异通过调用函数winnow( elements, qualifier, keep )时传入不同的参数keep来实现。
5368 not: function( selector ) {
5369 return this.pushstack( winnow(this, selector, false), "not",
selector );
5370 },
5371
5372 filter: function( selector ) {
5373 return this.pushstack( winnow(this, selector, true), "filter",
5374 },
5470 });
5590 // implement the identical functionality for filter and not
5591 function winnow( elements, qualifier, keep ) {
5592
5593 // can't pass null or undefined to indexof in firefox 4
5594 // set to 0 to skip string check
5595 qualifier = qualifier || 0;
5596
5597 if ( jquery.isfunction( qualifier ) ) {
5598 return jquery.grep(elements, function( elem, i ) {
5599 var retval = !!qualifier.call( elem, i, elem );
5600 return retval === keep;
5601 });
5602
5603 } else if ( qualifier.nodetype ) {
5604 return jquery.grep(elements, function( elem, i ) {
5605 return ( elem === qualifier ) === keep;
5606 });
5607
5608 } else if ( typeof qualifier === "string" ) {
5609 var filtered = jquery.grep(elements, function( elem ) {
5610 return elem.nodetype === 1;
5611 });
5612
5613 if ( issimple.test( qualifier ) ) {
5614 return jquery.filter(qualifier, filtered, !keep);
5615 } else {
5616 qualifier = jquery.filter( qualifier, filtered );
5617 }
5618 }
5619
5620 return jquery.grep(elements, function( elem, i ) {
5621 return ( jquery.inarray( elem, qualifier ) >= 0 ) === keep;
5622 });
5623 }
第5591行:函数winnow( elements, qualifier, keep )负责过滤元素集合,它接受3个参数:
参数elements:待过滤的元素集合。
参数qualifier:用于过滤元素集合elements,可选值有函数、dom元素、选择器表达式、dom元素数组、jquery对象。
参数keep:布尔值。如果为true,则保留匹配元素,如果为false,则保留不匹配元素。
第5597~5601行:如果参数qualifier是函数,则调用方法jquery.grep( array, function
( elementofarray, indexinarray )[, invert] )遍历元素集合elements,在每个元素上执行该函数,然后将返回值与参数keep进行比较,如果一致则保留,不一致则丢弃。
第5603~5606行:如果参数qualifier是dom元素,则调用方法jquery.grep( array, function( elementofarray, indexinarray )[, invert] )遍历元素集合elements,检查其中的每个元素是否与参数qualifier相等,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。
第5608~5622行:如果参数qualifier是字符串,则先过滤出与该参数匹配的元素集合,然后调用方法jquery.grep( array, function( elementofarray, indexinarray )[, invert] )遍历元素集合elements,检查其中的每个元素是否在过滤结果中,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。
第5620~5622行:如果参数qualifier是dom元素数组或jquery对象,则调用方法jquery.grep( array, function(elementofarray, indexinarray)[, invert] )遍历元素集合 elements,检查其中的每个元素是否在参数qualifier中,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。
关于方法jquery.grep( array, function(elementofarray, indexinarray)[, invert] )的介绍和分析请参见2.8.8节。
3.12.5 .is( selector )
方法.is( selector )用选择器表达式、dom元素、jquery对象或函数来检查当前匹配元素集合,只要其中某个元素可以匹配给定的参数就返回true。
5310 pos = jquery.expr.match.pos,
5376 is: function( selector ) {
5377 return !!selector && (
5378 typeof selector === "string" ?
5379 // if this is a positional selector, check membership in the returned set
5380 // so $("p:first").is("p:last") won't return true for a doc with two "p".
5381 pos.test( selector ) ?
5382 jquery( selector, this.context ).index( this[0] ) >= 0 :
5383 jquery.filter( selector, this ).length > 0 :
5384 this.filter( selector ).length > 0 );
5385 },
第5376行:定义方法.is( selector ),其中参数selector可以是选择器表达式、dom元素、jquery对象或函数。
第5378行、第5381行、第5382行:如果参数selector是字符串,并且含有位置伪类,则先调用构造函数jquery( selector[, context] )查找参数selector匹配的元素集合,然后检查当前匹配元素集合中的第一个元素是否在查找结果中,如果在结果中则返回true,如果不在则返回false。
第5378行、第5381行、第5383行:如果参数selector是字符串,并且不含有位置伪类,则调用方法jquery.filter( expr, elems, not )用参数selector过滤当前匹配元素集合,然后检查过滤结果中是否仍有元素,如果有则返回true,如果没有则返回false。
第5378行、第5384行:如果参数selector不是字符串,则可能是dom元素、jquery对象或函数,先调用方法.filter( selector )用参数selector过滤当前匹配元素集合,然后检查过滤结果中是否仍有元素,如果有则返回true,如果没有则返回false。
3.12.6 .closest( selectors, context )
方法.closest( selectors [, context] )用于在当前匹配元素集合和它们的祖先元素中查找与参数selectors匹配的最近元素,并用查找结果构造一个新jquery对象。
方法.closest( selectors [, context] )与.parents( [selector] )的行为相似,都是沿着dom树向上查找匹配元素,需要注意的是两者的差别,具体如表3-16所示。
表3-16 .closest( selectors [, context] )、.parents( [selector] )
序 号 .closest( selectors [, context] ) .parents( [selector] )
1 从每个匹配元素开始 从每个匹配元素的父元素开始
2 沿着dom树向上遍历,直到找到匹配参数selectors的元素 沿着dom树向上遍历,查找所有与参数selectors匹配的元素
5387 closest: function( selectors, context ) {
5388 var ret = [], i, l, cur = this[0];
5389
5390 // array (deprecated as of jquery 1.7)
5391 if ( jquery.isarray( selectors ) ) {
5392 var level = 1;
5393
5394 while ( cur && cur.ownerdocument && cur !== context ) {
5395 for ( i = 0; i < selectors.length; i++ ) {
5396
5397 if ( jquery( cur ).is( selectors[ i ] ) ) {
5398 ret.push({ selector: selectors[ i ], elem: cur, level: level });
5399 }
5400 }
5401
5402 cur = cur.parentnode;
5403 level++;
5404 }
5405
5406 return ret;
5407 }
5408
5409 // string
5410 var pos = pos.test( selectors ) || typeof selectors !== "string" ?
5411 jquery( selectors, context || this.context ) :
5412 0;
5413
5414 for ( i = 0, l = this.length; i < l; i++ ) {
5415 cur = this[i];
5416
5417 while ( cur ) {
5418 if ( pos ? pos.index(cur) > -1 : jquery.find.matchesselector (cur, selectors) ) {
5419 ret.push( cur );
5420 break;
5421
5422 } else {
5423 cur = cur.parentnode;
5424 if ( !cur || !cur.ownerdocument || cur === context || cur.nodetype === 11 ) {
5425 break;
5426 }
5427 }
5428 }
5429 }
5430
5431 ret = ret.length > 1 ? jquery.unique( ret ) : ret;
5432
5433 return this.pushstack( ret, "closest", selectors );
5434 },
第5387行:定义方法.closest( selectors [, context] ),它接受2个参数:
参数selectors:用于匹配dom元素,可选值有:选择器表达式、jquery对象、dom元素、数组。
参数context:可选的上下文,用于限定查找范围。
第5391~5407行:如果参数selectors是数组,则从当前匹配元素集合中的第一个元素开始沿着dom树向上遍历,查找与数组selector中的元素匹配的元素,直到遇到上下文context或document对象为止。如果找到与数组selector中的元素匹配的元素,则放入数组ret中,其格式为:
{ selector: 参数 selector 中的元素, elem: 匹配元素, level: 向上查找的层级 }
第5414~5429行:遍历当前元素集合,在每个匹配元素和它的祖先元素中查找与参数selectors匹配的最近元素。
第5417~5428行:在向上遍历的过程中,如果找到了与参数selectors匹配的元素,则把它插入数组ret,然后跳出while循环,继续在下一个匹配元素和它的祖先元素中查找。
第5422~5427行:如果当前元素cur不匹配参数selectors,则读取它的父元素,继续向上遍历。如果父元素不存在,或者父元素不在文档中,或者父元素是上下文,或者父元素是文档片段,则跳出while循环,继续在下一个匹配元素和它的祖先元素中查找。
第5431行:如果找到了多个匹配参数selectors的元素,则调用方法jquery.unique( results ),也就是sizzle.uniquesort( results ),执行排序和去重操作。
第5433行:最后用找到的元素构造一个新jquery对象,并返回。
3.12.7 .index( elem )
方法.index( elem )用于判断元素在元素集合中的下标位置。该方法的行为随参数elem的不同而不同,如表3-17所示。
表3-17 .index( elem )
序 号 参数 elem 行 为
1 没有传入 返回第一个匹配元素相对于其兄弟元素的下标位置
2 选择器表达式 返回第一个匹配元素在选择器表达式所匹配的元素集合中的位置
3 dom元素 返回dom元素在当前匹配元素集合中的位置
4 jquery对象 返回jquery对象的第一个元素在当前匹配元素集合中的位置
5436 // determine the position of an element within
5437 // the matched set of elements
5438 index: function( elem ) {
5439
5440 // no argument, return index in parent
5441 if ( !elem ) {
5442 return ( this[0] && this[0].parentnode ) ? this.prevall().length : -1;
5443 }
5444
5445 // index in selector
5446 if ( typeof elem === "string" ) {
5447 return jquery.inarray( this[0], jquery( elem ) );
5448 }
5449
5450 // locate the position of the desired element
5451 return jquery.inarray(
5452 // if it receives a jquery object, the first element is used
5453 elem.jquery ? elem[0] : elem, this );
5454 },
第5441~5443行:如果没有参数,则返回当前匹配元素集合中第一个元素相对于其兄弟元素的位置。如果第一个元素没有父元素,则返回-1。
第5446~5448行:如果参数elem是字符串,则调用构造函数jquery( selector[, context ] )查找参数elem匹配的元素集合,然后调用方法jquery.inarray( elem, array, i )获取当前匹配元素集合中第一个元素在查找结果中的位置,并返回。
第5451~5453行:如果参数elem是dom元素,则调用方法jquery.inarray( elem, array, i )获取参数elem在当前元素匹配集合中的位置,并返回;如果参数elem是jquery对象,则调用方法jquery.inarray( elem, array, i )获取参数elem的第一个元素在当前匹配元素集合中的位置,并返回。
3.12.8 .add( selector, context )
方法.add( selector, context )用当前jquery对象中的元素和传入的参数构造一个新jquery对象。构造函数jquery()可以接受的参数格式,该方法都可以接受。另外,该方法不会改变当前jquery对象。
5456 add: function( selector, context ) {
5457 var set = typeof selector === "string" ?
5458 jquery( selector, context ) :
5459 jquery.makearray( selector && selector.nodetype ?
[ selector ] : selector ),
5460 all = jquery.merge( this.get(), set );
5461
5462 return this.pushstack( isdisconnected( set[0] ) || isdisconnected
( all[0] ) ?
5463 all :
5464 jquery.unique( all ) );
5465 },
5472 // a painfully simple check to see if an element is disconnected
5473 // from a document (should be improved, where feasible).
5474 function isdisconnected( node ) {
5475 return !node || !node.parentnode || node.parentnode.nodetype === 11;
5476 }
第5457~5459行:如果参数selector是字符串,则调用构造函数jquery( selector[, context] )查找与之匹配的元素集合,并赋值给变量set;否则调用方法jquery.makearray( array, results )将参数selector转换为数组。
第5460行:调用方法jquery.merge( first, second )将当前jquery对象中的元素和元素集合set的元素合并,并赋值给变量all。
第5462~5464行:用合并后的元素集合all构造一个新jquery对象。如果元素集合set的第一个元素或合并后的元素集合all中第一个元素不在文档中,则可以直接用元素集合all构造新jquery对象;否则,需要先调用方法jquery.unique( results ),也就是sizzle.uniquesort( results ),对合并后的元素集合all进行排序和去重,然后再构造jquery对象。
第5474~5476行:函数isdisconnected( node )用于简单地判断一个元素是否不在文档中。如果元素不存在,或者父元素不存在,或者父元素是文档片段,则返回true。
3.12.9 jquery.filter( expr, elems, not )
方法jquery.filter( expr, elems, not )使用指定的选择器表达式expr对元素集合elems进行过滤,并返回过滤结果。如果参数not是true,则保留不匹配元素,否则默认保留匹配元素。
5540 jquery.extend({
5541 filter: function( expr, elems, not ) {
5542 if ( not ) {
5543 expr = ":not(" + expr + ")";
5544 }
5545
5546 return elems.length === 1 ?
5547 jquery.find.matchesselector(elems[0], expr) ? [ elems[0] ] : [] :
5548 jquery.find.matches(expr, elems);
5549 },
5588 });
第5541行:定义方法jquery.filter( expr, elems, not ),它接受3个参数:
参数expr:选择器表达式。
参数elems:待过滤的元素集合。
参数not:布尔值。如果是true,则保留不匹配元素,否则默认保留匹配元素。
第5542~5544行:修正选择器表达式expr。
第5546、5547行:如果元素集合elems中只有一个元素,则调用方法jquery.find.matches
selector( node, expr ),也就是sizzle.matchesselector( node, expr ),来检查元素是否匹配选择器表达式expr。
第5546~5548行:如果含有多个元素,则调用方法jquery.find.matches( expr, set ),也就是sizzle.matches( expr, set ),来用选择器表达式expr对元素集合elems进行过滤。
3.12.10 :animated
jquery的动画模块扩展了伪类:animated,用于检测dom元素是否正在执行动画。在对应的伪类过滤函数jquery.expr.filters.animated中,会遍历全局动画函数数组jquery.timers,检查每个动画函数的属性elem是否是当前元素;如果某个动画函数的属性elem是当前元素,则表示当前元素正在执行动画。更多信息请参考第14章。
8842 if ( jquery.expr && jquery.expr.filters ) {
8843 jquery.expr.filters.animated = function( elem ) {
8844 return jquery.grep(jquery.timers, function( fn ) {
8845 return elem === fn.elem;
8846 }).length;
8847 };
8848 }
3.12.11 hidden、:visible
jquery的样式操作模块扩展了伪类:hidden和:visible,用来判断dom元素是否占据布局空间。在对应的伪类过滤函数jquery.expr.filters.hidden/visible中,通过判断dom元素的可见宽度offsetwidth和可见高度offsetheight是否为0,来判断该元素是否占据布局空间。
注意:设置样式visibility为hidden,或设置样式opacity为0后,dom元素仍然会占据布局空间。
6816 if ( jquery.expr && jquery.expr.filters ) {
6817 jquery.expr.filters.hidden = function( elem ) {
6818 var width = elem.offsetwidth,
6819 height = elem.offsetheight;
6820
6821 return ( width === 0 && height === 0 )
|| (!jquery.support.reliablehiddenoffsets && ((elem.style && elem.style.display) || jquery.css( elem, "display" )) === "none");
6822 };
6823
6824 jquery.expr.filters.visible = function( elem ) {
6825 return !jquery.expr.filters.hidden( elem );
6826 };
6827 }
第6817~6822行:如果dom元素的可见宽度offsetwidth和可见高度offsetheight是0,则认为该元素不占据布局空间。如果元素的可见高度offsetheight不可靠,则检查样式display;如果内联样式display是"none",则认为不占据布局空间;如果未设置行内样式display,但是计算样式display是"none",也认为不占据布局空间。
第6824~6826行:通过对伪类过滤函数jquery.expr.filters.hidden( elem )的结果取反,来确定元素是否占据布局空间。
3.13 总结
在本章中,对选择器引擎sizzle做了完整的介绍和分析,总体源码结构见代码清单3-1,方法功能和调用关系见图3-1。在本章的最后还介绍和分析了jquery对sizzle的整合和扩展。
选择器表达式由块表达式和块间关系符组成。块表达式分为3种:简单表达式、属性表达式、伪类表达式;块间关系符分为4种:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素;块表达式和块间关系符组成了层级表达式。见图3-3。
sizzle( selector, context, results, seed )用于查找与选择器表达式selector匹配的元素集合。如果浏览器支持原生方法queryselectorall(),则调用该方法查找元素,如果不支持,则模拟该方法的行为。执行过程见图3-2。
正则chunker用于从选择器表达式中提取块表达式和块间关系符,其分解图和测试用例见图3-4。
sizzle.find( expr, context, isxml )负责查找与块表达式匹配的元素集合。该方法按照表达式类型数组sizzle.selectors.order规定的查找顺序(id、class、name、tag)逐个尝试查找,如果未找到,则查找上下文的所有后代元素(*)。执行过程见图3-5。
sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。该方法通过调用过滤函数集sizzle.selectors.filter中的过滤函数来执行过滤操作。执行过程见图3-6。
sizzle.selectors包含了sizzle在查找和过滤过程中用到的正则、查找函数、过滤函数,见图3-1。
sizzle.selectors.order中定义了查找单个块表达式时的查找顺序,依次是id、class、name、tag。其中,class需要浏览器支持方法getelementsbyclassname()。
sizzle.selectors.match/leftmatch中存放了表达式类型和正则的映射,正则用于确定块表达式的类型,并解析其中的参数。解析图见图3-7~图3-14,测试用例见表3-3~表3-11。
sizzle.selectors.find中定义了id、class、name、tag所对应的查找函数。其中,class需要浏览器支持方法getelementsbyclassname()。查找函数会返回数组或undefined,内部通过调用相应的原生方法来查找元素,见表3-11。
sizzle.selectors.relative中存放了块间关系符和对应的块间关系过滤函数。块间关系过滤函数用于检查映射集checkset中的元素是否匹配块间关系符左侧的块表达式。支持的块间关系符和对应的过滤方式见表3-2。
sizzle.selectors.prefilter中定义了class、id、tag、child、attr、pseudo、pos所对应的预过滤函数。预过滤函用于在过滤函数之前修正与过滤操作相关的参数,每种类型的预过滤函数的修正行为见表3-12;预过滤函数有3种返回值,见表3-13。
sizzle.selectors.filters中定义了一组伪类和对应的伪类过滤函数。伪类过滤函数负责检查元素是否匹配伪类,返回一个布尔值。
sizzle.selectors.setfilters中定义了一组位置伪类和对应的伪类过滤函数。位置伪类过滤函数通过比较下标来确定元素在集合中的位置,返回一个布尔值。
sizzle.selectors.filter中定义了pseudo、child、id、tag、class、attr、pos对应的过滤函数。过滤函数负责检查元素是否匹配过滤表达式,返回一个布尔值。