天天看點

jQuery技術内幕:深入解析jQuery架構設計與實作原理. 3.4 Sizzle( selector, context, results, seed )

<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 &amp;&amp; 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 =

/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^

&gt;+~,(\[\\]+)+|[&gt;+~])(\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 &gt; 1 &amp;&amp; origpos.exec( selector ) ) {

3917

3918        

if ( parts.length === 2 &amp;&amp; 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 &lt; 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 &amp;&amp;

parts.length &gt; 1 &amp;&amp;

                    context.nodetype === 9

&amp;&amp;

                    !contextxml &amp;&amp;

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 &amp;&amp; (parts[0] === "~" || parts[0] ===

"+") &amp;&amp; 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 &gt; 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 &amp;&amp; context.nodetype === 1 ) {

4000             for ( i = 0; checkset[i] != null;

i++ ) {

4001                 if ( checkset[i] &amp;&amp;

(checkset[i] === true || checkset[i].nodetype === 1 &amp;&amp; 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] &amp;&amp;

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。

繼續閱讀