jQuery技术内幕:深入解析jQuery架构设计与实现原理. 3.8 Sizzle.selectors.relative

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相等的元素,则其值变为找到的元素。

上一篇:远传技术助推广东电信客服中心运营管理再升级


下一篇:大咖齐聚“2017CIO时代中国行·上海站”,看新技术如何驱动企业转型