<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 && 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 =
/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^
>+~,(\[\\]+)+|[>+~])(\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 > 1 && origpos.exec( selector ) ) {
3917
3918
if ( parts.length === 2 && 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 < 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 &&
parts.length > 1 &&
context.nodetype === 9
&&
!contextxml &&
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 && (parts[0] === "~" || parts[0] ===
"+") && 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 > 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 && context.nodetype === 1 ) {
4000 for ( i = 0; checkset[i] != null;
i++ ) {
4001 if ( checkset[i] &&
(checkset[i] === true || checkset[i].nodetype === 1 && 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] &&
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。