天天看點

jQuery技術内幕:深入解析jQuery架構設計與實作原理. 3.8 Sizzle.selectors.relative

<b>3.8 sizzle.selectors.relative</b>

對象sizzle.selectors.relative中存放了塊間關系符和對應的塊間關系過濾函數,稱為“塊間關系過濾函數集”。

塊間關系符共有4種,其含義和過濾方式如表3-2所示。

圖3-6 sizzle.filter( expr, set, inplace, not )的執行過程

表3-2 塊間關系符的含義和過濾方式

序号         塊間關系符     選擇器表達式         說  明         從右向左的過濾方式

1       ""     ancestor descendant       比對所有後代元素         檢查祖先元素是否比對左側的塊表達式

2       "+"   prev + next       比對下一個兄弟元素     檢查前一個兄弟元素否比對左側的塊表達式

3       "&gt;"   parent &gt; 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 &amp;&amp;

!rnonword.test( part ),

4255                 ispartstrnottag = ispartstr

&amp;&amp; !istag;

4256

4257             if ( istag ) {

4258                 part = part.tolowercase();

4259             }

4260

4261             for ( var i = 0, l =

checkset.length, elem; i &lt; l; i++ ) {

4262                 if ( (elem = checkset[i]) ) {

4263                     while ( (elem = elem.previoussibling)

&amp;&amp; elem.nodetype !== 1 ) {}

4264

4265                     checkset[i] =

ispartstrnottag || elem &amp;&amp; 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 "&gt;"

塊間關系符"&gt;"用于選擇器"parent &gt; child",即比對父元素parent下的子元素child。例如,$("div

+ span")、$(".lastdiv + span")。對于從右向左的查找方式,則是檢查子元素child的父元素是否比對塊表達式parent。

4276        

"&gt;": function( checkset, part ) {

4277             var elem,

4278                 ispartstr = typeof part ===

4279                 i = 0,

4280                 l = checkset.length;

4281

4282             if ( ispartstr &amp;&amp;

!rnonword.test( part ) ) {

4283                 part = part.tolowercase();

4284

4285                 for ( ; i &lt; 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 &lt; 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" &amp;&amp; !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 ===

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 &lt; 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 &gt; 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。

checkset, part )中,當參數part是标簽時,才會調用函數dirnodecheck()。

5168 function

dirnodecheck( dir, cur, donename, checkset, nodecheck, isxml ) {

5169    

5170        

5171

5172        

5173             var match = false;

5174

5175             elem = elem[dir];

5176

5177             while ( elem ) {

5178                 if ( elem[ expando ] ===

5179                     match =

checkset[elem.sizset];

5180                     break;

5181                 }

5182

5183                 if ( elem.nodetype === 1

&amp;&amp; !isxml ){

5184                     elem[ expando ] =

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相等的元素,則其值變為找到的元素。

繼續閱讀