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

    xiaoxiao2023-12-28  171

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

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)