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中的元素是否匹配块间关系符左侧的块表达式。调用时的参数格式为:
Sizzle.selectors.relative[ 块间关系符 cur ]( 映射集 checkSet, 左侧块表达式 pop, contextXML );
块间关系过滤函数接受3个参数:
参数checkSet:映射集,对该元素集合执行过滤操作。
参数part:大多数情况下是块间关系符左侧的块表达式,该参数也可以是DOM元素。
参数isXML:布尔值,指示是否运行在一个XML文档中。
块间关系过滤函数实现的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。
相关代码如下所示:
4221 var Expr = Sizzle.selectors = {
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 },
4749 };
第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。
相关代码如下所示:
4221 var Expr = Sizzle.selectors = {
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 },
4338 },
4749 };
第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。
相关代码如下所示:
4221 var Expr = Sizzle.selectors = {
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 },
4338 },
4749 };
第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 }
4338 },
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。在后面的代码中没有用到该参数。
参数isXML:布尔值,指示是否运行在一个XML文档中。
第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个参数:
参数dir:表示查找方向的字符串,例如,“parentNode”、“previousSibling”。
参数cur:标签字符串。
参数doneName:数值。本次查找的唯一标识,用于优化查找过程,避免重复查找。
参数checkSet:候选集,在查找过程中,其中的元素将被替换为与参数cur匹配的元素或false。
参数nodeCheck:标签字符串。在后边的代码中没有用到该参数。
参数isXML:布尔值,指示是否运行在一个XML文档中。
第5169~5198行:遍历候选集checkSet,对其中的每个元素,沿着某个方向(例如,“parentNode”、“previousSibling”)一直查找,直到找到节点名称nodeName与参数cur相等的元素,或者在该方向上不再有元素为止。如果找到,则替换映射集checkSet中对应位置的元素为找到的元素;如果未找到,则默认替换为false。
第5178~5186行:在查找过程中,如果遇到已经检查过的元素,则直接取该元素在候选集checkSet中对应位置上的元素,避免重复查找。
第5196行:替换映射集checkSet中对应位置的元素。变量match初始值为false,如果找到节点名称nodeName与参数cur相等的元素,则其值变为找到的元素。