<b>3.4 sizzle( selector,</b>
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,
相关代码如下所示:
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
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
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
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
4007 for ( i = 0; checkset[i] != null;
4008 if ( checkset[i] &&
checkset[i].nodetype === 1 ) {
4009 results.push( set[i] );
4010 }
4011 }
4012
4013
4014
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
4024 };
函数sizzle( selector, context, results, seed )的执行过程可以总结为图3-3。