<b>3.9 sizzle.selectors</b>
對象sizzle.selectors包含了sizzle在查找和過濾過程中用到的正則、查找函數、過濾函數,其中包含的屬性見圖3-1,源碼結構見代碼清單3-1。
3.9.1 sizzle.selectors.order
表達式類型數組sizzle.selectors.order中定義了查找單個塊表達式時的查找順序,依次是id、class、name、tag。其中,class需要浏覽器支援方法getelementsbyclass
name()。查找順序綜合考慮了浏覽器是否支援、查找結果集的大小、查找效率、使用頻率等因素。
相關代碼如下所示:
4221 var expr =
sizzle.selectors = {
4222
order: [ "id", "name", "tag" ],
4749 };
5139 (function(){
5140
var div = document.createelement("div");
5141
5142
div.innerhtml = "<div class='test e'></div><div
class='test'></div>";
5143
5144
// opera can't find a second classname (in 9.6)
5145
// also, make sure that getelementsbyclassname actually exists
5146
if ( !div.getelementsbyclassname ||
div.getelementsbyclassname("e").length === 0 ) {
5147
return;
5148
}
5149
5150
// safari caches class attributes, doesn't catch changes (in 3.2)
5151
div.lastchild.classname = "e";
5152
5153
if ( div.getelementsbyclassname("e").length === 1 ) {
5154
5155
5156
5157
expr.order.splice(1, 0, "class");
5158
expr.find.class = function( match, context, isxml ) {
5159
if ( typeof context.getelementsbyclassname !== "undefined"
&& !isxml ) {
5160 return
context.getelementsbyclassname(match[1]);
5161
5162
};
5163
5164
// release memory in ie
5165
div = null;
5166 })();
第5140~5155行:測試目前浏覽器是否正确支援方法getelementsbyclassname()。測試思路是先構造一段dom結構,然後調用方法getelementsbyclassname(),檢查是否傳回期望數量的元素。如果不支援或不能正确支援,則不做任何事情。
第5157~5162行:如果目前浏覽器支援方法getelementsbyclassname(),則:
向sizzle.selectors.order中插入"class",由["id", "name", "tag"]變為["id",
"class", "name", "tag"],插入位置在"id"之後、"name"之前。
向sizzle.selectors.find中插入"class"對應的查找函數。
3.9.2 sizzle.selectors.match/leftmatch
對象sizzle.selectors.match/leftmatch中存放了表達式類型和正則的映射,正則用于确定塊表達式的類型,并解析其中的參數。
4221 var expr = sizzle.selectors = {
4224
match: {
4225
id: /#((?:[\w\u00c0-\uffff\-]|\\.)+)/,
4226
class: /\.((?:[\w\u00c0-\uffff\-]|\\.)+)/,
4227
name: /\[name=['"]*((?:[\w\u00c0-\uffff\-]|\\.)+)['"]*\]/,
4228
attr:
/\[\s*((?:[\w\u00c0-\uffff\-]|\\.)+)\s*(?:(\s?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uffff\-]|\\.)*)|)|)\s*\]/,
4229
tag: /^((?:[\w\u00c0-\uffff\*\-]|\\.)+)/,
4230
child: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|
(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
4231
pos: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
4232
pseudo: /:((?:[\w\u00c0-\uffff\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]
*)+)\2\))?/
4233
},
4749 };
4751 var origpos = expr.match.pos,
4752
fescape = function(all, num){
4753
return "\\" + (num - 0 + 1);
4754
};
4755
4756 for ( var type in expr.match ) {
4757
expr.match[ type ] = new regexp(
expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
4758
expr.leftmatch[ type ] = new regexp( /(^(?:.|\r|\n)*?)/.source +
expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
4759 }
第4224~4233行:定義一組正則,稍後會逐個分析和測試。
第4756~4759行:為對象sizzle.sectors.match中的正則增加一段字尾正則/(?![^\[]*\])(?![^\()*\])/,然後再加上一段字首正則/(^(?:.|\r|\n)*?)/,來構造對象sizzle.sectors.leftmatch中的同名正則。因為增加的字首正則中包含了一個分組,是以原正則中的分組編号需要加1後移。
1.?字尾正則/(?![^\[]*\])(?![^\()*\])/
字尾正則/(?![^\[]*\])(?![^\()*\])/要求接下來的字元不能含有"]"、")",用于確定選擇器表達式的文法正确,以及確定正确比對嵌套選擇器表達式。
例如,執行$("input[name=foo\\.baz]]")時會抛出文法異常,因為選擇器表達式"input
[name=foo\\.baz]]"的末尾多了一個"]",對象sizzle.selectors.match/leftmatch中沒有正則可以比對其中的"[name=foo\\.baz]]";如果沒有字尾正則,則sizzle.selectors.match/leftmatch.name會比對"[name=foo\\.baz]]",并執行查找,而不會抛出文法異常。
又如,執行$("input[name=foo.baz]")時會抛出文法異常,因為選擇器表達式"input
[name=foo.baz]"沒有轉義點号,對象sizzle.selectors.match/leftmatch中沒有正則可以比對其中的"[name=foo.baz]";如果沒有字尾正則,則對象sizzle.selectors.match/leftmatch.class會比對"input[name=foo.baz]"中的".baz",并執行查找,然後用"input[name=foo
]"過濾查找結果,而不會抛出文法異常。
再如,執行$("input:not(.foo)")時,會先查找比對"input"的元素集合,然後從中過濾不比對".foo"的元素集合;如果沒有字尾正則,則會變為先查找比對".foo"的元素集合,然後從中過濾比對"input:not()"的元素集合。
讀者可以将第4757行代碼注釋掉,然後測試和驗證上述例子。
2.?字首正則/(^(?:.|\r|\n)*?)/
字首正則/(^(?:.|\r|\n)*?)/用于捕獲比對正則的表達式之前的字元,主要是捕獲轉義反斜杠,以支援将特殊字元作為普通字元使用。
例如,".test\\#id",在用正則sizzle.selectors.match.id比對時會發現"#"之前是轉義反斜杠"\\",這時将認為該表達式的類型不是id;如果沒有字首正則,則會先查找比對"#id"的元素集合,然後從中過濾出比對".test\\"的元素集合。
接下來分析和測試對象sizzle.selectors.match中id、class、name、attr、tag、child、pos、pseudo對應的正則。測試用例參考自sizzle的測試用例https://github.com/jquery/sizzle/blob/1.7.1/test/unit/selector.js。
3.?id
正則sizzle.selector.match.id用于比對簡單表達式"#id",并解析"#"之後的字元串,其中含有1個分組:id。解析圖見圖3-7,測試用例見表3-3。
圖3-7 正則sizzle.selectors.match.id
表3-3 正則 sizzle.selectors.match.id
序 号 測 試 用 例 運作結果
1 id.exec("#id") ["#id", "id"]
2 id.exec("#firstp#simon1") ["#firstp", "firstp"]
3 id.exec("#台北ta?ibe?i") ["#台北ta?ibe?i",
"台北ta?ibe?i"]
4 id.exec("#foo\\:bar") ["#foo\\:bar",
"foo\\:bar"]
5 id.exec("#test\\.foo\\[5\\]bar") ["#test\\.foo\\[5\\]bar",
"test\\.foo\\[5\\]bar"]
4.?class
正則 sizzle.selector.match.class 用于比對簡單表達式".class",并解析"."之後的字元串,其中含有1個分組:類樣式。解析圖見圖3-8,測試用例見表3-4。
圖3-8 正則sizzle.selectors.match.class
表3-4 正則
sizzle.selectors.match.class
序 号 測 試 用 例 運
行 結 果
1 class.exec(".blog") [".blog", "blog"]
2 class.exec(".blog.link") [".blog", "blog"]
3 class.exec(".台北ta?ibe?i") [".台北ta?ibe?i",
4 class.exec(".foo\\:bar") [".foo\\:bar",
5 class.exec(".test\\.foo\\[5\\]bar") [".test\\.foo\\[5\\]bar",
5.?name
正則sizzle.selector.match.name用于比對屬性表達式"[ name
= "value" ]",并解析屬性name的值,其中含有1個分組:屬性name的值。解析圖見圖3-9,測試用例見表3-5。
圖3-9 正則sizzle.selectors.match.name
表3-5 正則 sizzle.selectors.match.name
1 name.exec("input[name=action]") ["[name=action]", "action"]
2 name.exec("input[name='action']") ["[name='action']",
"action"]
3 name.exec("input[name=\"action\"]") ["[name="action"]",
4 name.exec("input[name=\"types[]\"]") null
6.?attr
正則sizzle.selector.match.attr用于比對屬性表達式"[attribute
= "value"]",并解析屬性名和屬性值,其中含有5個分組:屬性名、等号部分、引号、屬性值、無引号時的屬性值。解析圖見圖3-10,測試用例見表3-6。
7.tag
正則sizzle.selector.match.tag用于比對簡單表達式"tag",并解析标簽名,其中含有1個分組:标簽名。解析圖見圖3-11,測試用例見表3-7。
8.?child
正則sizzle.selector.match.child用于比對子元素僞類表達式:nth-child(index/even/odd/equation)、:first-child、:last-child、:only-child,并解析子元素僞類和僞類參數,其中含有2個分組:子元素僞類、僞類參數。解析圖見圖3-12,測試用例見表3-8。
9.?pos
正則sizzle.selector.match.pos用于比對位置僞類表達式":eq(index)"、":gt(index)"、":lt(index)"、":first"、":last"、":odd"、":even",并解析位置僞類和僞類參數,其中含有2個分組:位置僞類、僞類參數。解析圖見圖3-13,測試用例見表3-9。
10.?pseudo
正則 sizzle.selector.match.pseudo 用于比對僞類表達式,請解析
":" 之後的僞類和僞類參數,其中含有 3 個分組:僞類、引号、僞類參數。解析圖見圖3-14,測試用例見表3-10。
表3-6 正則sizzle.selectors.match.attr
序号 測 試 用 例 運
1 attr.exec("a[title]") ["[title]", "title",
undefined, undefined, undefined, undefined]
2 attr.exec("a[title=]") ["[title=]", "title",
"=", undefined, undefined, ""]
3 attr.exec("a[rel='bookmark']") ["[rel='bookmark']",
"rel", "=", "'", "bookmark", undefined]
4 attr.exec("a[rel=\"bookmark\"]") ["[rel="bookmark"]",
"rel", "=", """, "bookmark",
undefined]
5 attr.exec("a[rel=bookmark]") ["[rel=bookmark]",
"rel", "=", undefined, undefined, "bookmark"]
6 attr.exec("a[rel='bookmark']") ["[rel='bookmark']",
7 attr.exec("input[name=foo\\.baz]") ["[name=foo\\.baz]",
"name", "=", undefined, undefined, "foo\\.baz"]
8 attr.exec("input[name=foo\\[baz\\]]") ["[name=foo\\[baz\\]]",
"name", "=", undefined, undefined, "foo\\
[baz\\]"]
9 attr.exec("a[href='http://www.google.com/']") ["[href='http://www.google.com/']",
"href", "=", "'",
"http://www.google.com/", undefined]
10 attr.exec("a[href^='http://www']") ["[href^='http://www']",
"href", "^=", "'", "http://www",
11 attr.exec("a[href$='org/']") ["[href$='org/']", "href",
"$=", "'", "org/", undefined]
12 attr.exec("a[href*='google']") ["[href*='google']",
"href", "*=", "'", "google", undefined]
13 attr.exec("option[value='']") ["[value='']", "value",
"=", "'", "", undefined]
14 attr.exec("option[value!='']") ["[value!='']",
"value", "!=", "'", "", undefined]
15 attr.exec("[xml\\:test]") ["[xml\\:test]",
"xml\\:test", undefined, undefined, undefined, undefined]
16 attr.exec("[data-foo]") ["[data-foo]", "data-foo",
圖3-11 正則sizzle.selectors.match.tag
表3-7 正則 sizzle.selectors.match.tag
1 tag.exec("body") ["body", "body"]
2 tag.exec("html") ["html", "html"]
3 tag.exec("h1") ["h1", "h1"]
表3-8 正則
sizzle.selectors.match.child
1 child.exec("p:first-child") [":first-child",
"first", undefined]
2 child.exec("p:only-child") [":only-child",
"only", undefined]
3 child.exec("option:nth-child") [":nth-child", "nth",
4 child.exec("option:nth-child(even)") [":nth-child(even)",
"nth", "even"]
5 child.exec("option:nth-child(odd)") [":nth-child(odd)",
"nth", "odd"]
6 child.exec("option:nth-child(1)") [":nth-child(1)", "nth",
"1"]
7 child.exec("option:nth-child(+1)") [":nth-child(+1)",
"nth", "+1"]
8 child.exec("option:nth-child(-1)") [":nth-child(-1)", "nth",
"-1"]
9 child.exec("option:nth-child(0n+3)") [":nth-child(0n+3)",
"nth", "0n+3"]
10 child.exec("option:nth-child(1n)") [":nth-child(1n)",
"nth", "1n"]
11 child.exec("option:nth-child(n)") [":nth-child(n)", "nth",
"n"]
12 child.exec("option:nth-child(+n)") [":nth-child(+n)",
"nth", "+n"]
13 child.exec("option:nth-child(-1n
+ 3)") [":nth-child-1n +
3)", "nth", "-1n + 3"]
14 child.exec("option:nth-child(-n+3)") [":nth-child(-n+3)",
"nth", "-n+3"]
15 child.exec("option:nth-child(-1n+3)") [":nth-child(-1n+3)",
"nth", "-1n+3"]
16 child.exec("option:nth-child(2n)") [":nth-child(2n)",
"nth", "2n"]
17 child.exec("option:nth-child(2n
+ 1)") [":nth-child(2n+1)",
"nth", "2n+1"]
18 child.exec("option:nth-child(2n
+ 1)") [":nth-child(2n +
1)", "nth", "2n + 1"]
19 child.exec("option:nth-child(+2n+1)") [":nth-child(+2n + 1)",
"nth", "+2n + 1"]
20 child.exec("option:nth-child(3n)") [":nth-child(3n)",
"nth", "3n"]
21 child.exec("option:nth-child(3n+0)") [":nth-child(3n+0)",
"nth", "3n+0"]
22 child.exec("option:nth-child(3n+1)") [":nth-child(3n+1)",
"nth", "3n+1"]
23 child.exec("option:nth-child(3n-0)") [":nth-child(3n-0)",
"nth", "3n-0"]
24 child.exec("option:nth-child(3n-1)") [":nth-child(3n-1)",
"nth", "3n-1"]
圖3-13 正則sizzle.selectors.match.pos
表3-9 正則 sizzle.selectors.match.pos
1 pos.exec("p:nth(1)") [":nth(1)", "nth",
2 pos.exec("p:eq(2)") [":eq(2)", "eq",
"2"]
3 pos.exec("p:gt(3)") [":gt(3)", "gt",
"3"]
4 pos.exec("p:lt(4)") [":lt(4)", "lt",
"4"]
5 pos.exec("p:first") [":first", "first",
6 pos.exec("p:last") [":last", "last",
7 pos.exec("p:even") [":even", "even",
8 pos.exec("p:odd") [":odd", "odd",
圖3-14 正則 sizzle.selectors.match.pseudo
表3-10 正則 sizzle.selectors.match.pseudo
1 pseudo.exec("p:has(a)") [":has(a)", "has",
"", "a"]
2 pseudo.exec("a:contains(google)") [":contains(google)",
"contains", "", "google"]
3 pseudo.exec("input:focus") [":focus", "focus",
undefined, undefined]
4 pseudo.exec(":input") [":input", "input",
5 pseudo.exec(":radio") [":radio", "radio",
6 pseudo.exec(":checkbox") [":checkbox",
"checkbox", undefined, undefined]
7 pseudo.exec(":text") [":text", "text",
8 pseudo.exec(":radio:checked") [":radio", "radio",
9 pseudo.exec(":checkbox:checked") [":checkbox",
10 pseudo.exec("option:selected") [":selected",
"selected", undefined, undefined]
11 pseudo.exec(":header") [":header",
"header", undefined, undefined]
12 pseudo.exec(":empty") [":empty", "empty",
13 pseudo.exec(":parent") [":parent",
"parent", undefined, undefined]
14 pseudo.exec(":hidden") [":hidden",
"hidden", undefined, undefined]
15 pseudo.exec(":visible") [":visible", "visible",
3.9.3 sizzle.selectors.find
對象sizzle.selectors.find 中定義了id、class、name、tag所對應的查找函數,稱為“查找函數集”。其中,class需要浏覽器支援方法getelementsbyclassname()。
查找函數會傳回元素集合或 undefined,内部通過調用相應的原生方法來查找元素,如表3-11所示。查找函數調用原生方法前會檢查上下文是否支援原生方法。
表3-11 查找函數集
sizzle.selectors.find
序 号 類型 原 生 方 法 說 明
1 id getelementbyid() 查找擁有指定id的第一個元素
2 class getelementsbyclassname() 查獲擁有指定類樣式的元素集合
3 name getelementsbyname() 查獲擁有指定name的元素集合
4 tag getelementsbytagname() 查找擁有指定标簽名的元素集合
1.?id
4340
find: {
4341
id: function( match, context, isxml ) {
4342 if ( typeof context.getelementbyid
!== "undefined" && !isxml ) {
4343 var m =
context.getelementbyid(match[1]);
4344 // check parentnode to catch
when blackberry 4.6 returns
4345 // nodes that are no longer in
the document #6963
4346 return m && m.parentnode
? [m] : [];
4347 }
4348
4370
2.?class
5139 (function(){
// 測試浏覽器是否支援方法 getelementsbyclassname()
// 如果不支援,則不做任何事情
// 如果目前浏覽器支援方法 getelementsbyclassname()
5162
3.?name
4350
name: function( match, context ) {
4351 if ( typeof
context.getelementsbyname !== "undefined" ) {
4352 var ret = [],
4353 results =
context.getelementsbyname( match[1] );
4354
4355 for ( var i = 0, l =
results.length; i < l; i++ ) {
4356 if (
results[i].getattribute("name") === match[1] ) {
4357
ret.push(
results[i] );
4358 }
4359 }
4360
4361 return ret.length === 0 ? null
: ret;
4362 }
4363
4364
4.?tag
4365
tag: function( match, context ) {
4366 if ( typeof
context.getelementsbytagname !== "undefined" ) {
4367 return
context.getelementsbytagname( match[1] );
4368 }
4369
3.9.4 sizzle.selectors.prefilter
對象sizzle.selectors.prefilter中定義了類型class、id、tag、child、attr、pseudo、pos所對應的預過濾函數,稱為“預過濾函數集”。
在方法sizzle.filter( expr, set, inplace, not )中,預過濾函數在過濾函數sizzle.selectors.filter[
type ]之前被調用,見圖3-6。調用預過濾函數時的參數格式為:
sizzle.selectors.prefilter[ type ]( 正則比對結果 match,
元素集合 curloop, 是否縮小元素集合 inplace, 新集合 result, 是否取反 not, isxml )
預過濾函數用于在過濾函數之前修正與過濾操作相關的參數,每種類型的預過濾函數其修正行為如表3-12所示。
表3-12 預過濾函數集
sizzle.selectors.prefilter
序 号 類 型 修 正 行 為 序 号 類 型 修 正 行 為
1 class 過濾不比對元素,或縮小元素集合 5 attr 修正屬性名和屬性值
2 id 過濾轉義反斜杠 6 pseudo 處理:not( selector )的僞類參數
3 tag 過濾轉義反斜杠,轉為小寫 7 pos 修正位置僞類的參數下标
4 child 格式化子元素僞類參數
預過濾函數有3種傳回值,對應的含義如表3-13所示。
表3-13 預過濾函數的傳回值
序 号 返 回 值 說 明
1 false 已經執行過濾,或已經縮小候選集,不需要再執行過濾函數,例如,class
2 true 需要繼續執行其他的預過濾函數,尚不到執行過濾函數的時候,例如,在pseudo預過濾函數中遇到pos、child時
3 其他 可以調用對應的過濾函數
對象sizzle.selectors.prefilter的總體源碼結構如下所示:
var expr = sizzle.selectors = {
prefilter: {
class: function( match, curloop, inplace, result, not, isxml ) { ... },
id: function( match ) { ... },
tag: function( match, curloop ) {
... },
child: function( match ) { ... },
attr: function( match, curloop, inplace, result, not, isxml ) { ... },
pseudo: function( match, curloop, inplace, result, not ) { ... },
pos: function( match ) { ... }
},
下面對其中的預過濾函數逐個進行介紹和分析。
1.?class
類樣式預過濾函數sizzle.selectors.prefilter.class( match, curloop, inplace, result,
not, isxml )負責檢查元素集合中的每個元素是否含有指定的類樣式。如果參數inplace為true,則将不比對的元素替換為false;如果參數inplace不是true,則将比對元素放入元素集合result中,以此來不斷地縮小元素集合。關于正則sizzle.selectors.match/leftmatch.class的說明請參見3.9.2節。
3866
rbackslash = /\\/g,
4371
4372
class: function( match, curloop, inplace, result, not, isxml ) {
4373 match = " " +
match[1].replace( rbackslash, "" ) + " ";
4374
4375 if ( isxml ) {
4376 return match;
4377 }
4378
4379
for ( var i = 0, elem;
(elem = curloop[i]) != null; i++ ) {
4380 if ( elem ) {
4381 if ( not ^ (elem.classname
&& (" " + elem.classname +
" "). replace(/[\t\n\r]/g, "
").indexof(match) >= 0) ) {
4382 if ( !inplace ) {
4383 result.push( elem
);
4384 }
4385
4386 } else if ( inplace ) {
4387 curloop[i] = false;
4388 }
4389
}
4390 }
4391
4392 return false;
4393
4475
第4373行:檢查類樣式的技巧是在前後加空格,然後用字元串方法indexof()進行判斷。
第4379~4390行:周遊元素集合curloop,檢測每個元素是否含有指定名稱的類樣式;如果參數not不是true,則保留比對元素,并排除不比對元素;如果參數not是true,則保留不比對元素,排除比對元素。如果參數inplace為true,則将不比對的元素替換為false;如果參數inplace不是true,則不修改元素集合curloop,而是将比對元素放入元素集合result中,以此來不斷地縮小元素集合。
第4381~4388行:這段if-else-if語句塊的邏輯有些繞,可以這樣了解:if代碼塊表示的是過濾時通過了的情況,else-if語句塊表示的是過濾時未通過的情況。
第4392行:class預過濾函數總是傳回false,表示已經執行過濾,或已縮小候選集,不需要再執行class過濾函數。
2.?id
id預過濾函數sizzle.selectors.prefilter.id(
match )負責過濾轉義反斜杠,從比對結果match中提取并傳回id值。關于正則sizzle.selectors.match/leftmatch.id的具體說明請參見3.9.2節。
4395
id: function( match ) {
4396 return match[1].replace(
rbackslash, "" );
4397
3.?tag
标簽預過濾函數sizzle.selectors.prefilter.tag( match, curloop )負責過濾轉義反斜杠,轉換為小寫,從比對結果match中提取并傳回标簽名。關于正則sizzle.selectors.match/left
match.tag的具體說明參見3.9.2節。
4399
tag: function( match, curloop ) {
4400 return match[1].replace(
rbackslash, "" ).tolowercase();
4401
4.?child
子元素僞類預過濾函數sizzle.selectors.prefilter.child( match )負責将僞類:nth-child
( index/even/odd/equation )的參數格式化為first*n
+ last,例如,将odd格式化為2n+1。關于正則sizzle.selectors.match/leftmatch.child的具體說明請參見3.9.2節。
4403
child: function( match ) {
4404 if ( match[1] === "nth"
) {
4405 if ( !match[2] ) {
4406 sizzle.error( match[0] );
4407 }
4408
4409 match[2] =
match[2].replace(/^\+|\s*/g, '');
4410
4411 // parse equations like
'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
4412 var test =
/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
4413 match[2] ===
"even" && "2n" ||
match[2] ===
"odd" && "2n+1" ||
4414 !/\d/.test( match[2] )
&& "0n+" + match[2] ||
match[2]);
4415
4416 // calculate the numbers
(first)n+(last) including if they are negative
4417 match[2] = (test[1] + (test[2]
|| 1)) - 0;
4418 match[3] = test[3] - 0;
4419 }
4420 else if ( match[2] ) {
4421 sizzle.error( match[0] );
4422 }
4423
4424 // todo: move to normal caching
system
4425
match[0] = done++;
4426
4427 return match;
4428
第4409行:替換僞類開頭的加号和包含的空格,例如,:nth-child(+1)→:nth-child(1)、
:nth-child(2n + 1)→:nth-child(2n+1)。
第4412~4414行:将僞類參數統一格式化為first*n + last,例如,even→2n、odd→
2n+1、數字→0n+數字。正則/(-?)(\d*)(?:n([+\-]?\d*))?/含有3個分組:負号、first部分、last部分。
第4417~4418行:計算first部分和last部分。注意減0是為了将字元串強制轉換為數值。
第4425行:為本次過濾配置設定一個唯一的辨別,用于優化過濾過程,請參見3.9.7節對子元素僞類過濾函數sizzle.selectors.filter.child( elem, match )的介紹和分析。
5.?attr
屬性預過濾函數sizzle.selectors.prefilter.attr( match, curloop, inplace, result,
not, isxml )負責修正比對結果match中的屬性名和屬性值。關于正則sizzle.selectors.match/leftmatch.attr的具體說明請參見3.9.2節。
4430
attr: function( match, curloop, inplace, result, not, isxml ) {
4431 var name = match[1] =
match[1].replace( rbackslash, "" );
4432
4433 if ( !isxml &&
expr.attrmap[name] ) {
4434 match[1] = expr.attrmap[name];
4435 }
4436
4437 // handle if an un-quoted value
was used
4438 match[4] = ( match[4] || match[5]
|| "" ).replace( rbackslash, "" );
4439
4440 if ( match[2] === "~=" )
{
4441 match[4] = " " + match[4] +
" ";
4442 }
4443
4444 return match;
4445
第4431~4435行:修正屬性名。删除轉義反斜杠,修正某些特殊屬性名。
第4438~4442行:修正屬性值。合并分組4和分組5的值,删除轉義反斜杠。當屬性表達式的屬性值有引号時,屬性值存儲在match[4],否則存儲在match[5]。如果等号部分是~=,表示是單詞比對,則在屬性值前後加空格;在過濾函數中,對于~=,會在元素的屬性值前後加空格,然後用字元串方法indexof()檢查。
6.?pseudo
僞類預過濾函數sizzle.selectors.prefilter.pseudo( match, curloop, inplace, result,
not )主要負責處理僞類表達式是:not( selector )的情況,該函數會将比對結果match中的分組3(即僞類參數selector)替換為與之比對的元素集合;對于位置僞類和子元素僞類,則傳回true,繼續執行各自對應的預過濾函數。關于正則sizzle.selectors.match/leftmatch.pseudo的具體說明請參見3.9.2節。
4447
pseudo: function( match, curloop, inplace, result, not ) {
4448 if ( match[1] === "not"
4449 // if we're dealing with a
complex expression, or a simple one
4450 if ( ( chunker.exec(match[3])
|| "" ).length > 1 || /^\w/.test(match[3]) ) {
4451 match[3] =
sizzle(match[3], null, null, curloop);
4452
4453 } else {
4454 var ret =
sizzle.filter(match[3], curloop, inplace, true ^ not);
4455
4456 if ( !inplace ) {
4457 result.push.apply(
result, ret );
4458 }
4459
4460 return false;
4461 }
4462
4463 } else if ( expr.match.pos.test(
match[0] ) || expr.match.child.test( match[0] ) ) {
4464 return true;
4465 }
4466
4467 return match;
4468
第4447行:參數match是正則sizzle.selectors.match.pseudo比對塊表達式的結果,含有3個分組:僞類、引号、僞類參數。
第4448~4461行:如果僞類是:not(selector),則将比對結果match中的分組3(即僞類參數selector)替換為與之其比對的元素集合。在對應的過濾函數中,會篩選出不在分組3中的元素。
第4463~4465行:如果是位置僞類pos或子元素僞類child,則傳回true,表示仍然需要繼續執行各自所對應的預過濾函數。注意,位置僞類pos和子元素僞類child有着自己的預過濾函數。
7.?pos
位置僞類預過濾函數sizzle.selectors.prefilter.pos( match )負責在比對結果match的頭部插入一個新元素true,使得比對結果match中位置僞類參數的下标變為了3,進而與僞類的比對結果保持一緻。關于正則sizzle.selectors.match/leftmatch.pos/pseudo的具體說明請參見3.9.2節。
4470
pos: function( match ) {
4471 match.unshift( true );
4472
4473 return match;
4474
3.9.5 sizzle.selectors.filters
對象sizzle.selectors.filters中定義了一組僞類和對應的僞類過濾函數,稱為“僞類過濾函數集”。支援的僞類有::enabled、:disabled、:checked、:selected、:parent、:empty、:has、:header、:text、:radio、:checkbox、:file、:password、:submit、:image、:reset、:button、:input、:focus。方法調用鍊為:sizzle.filter()→sizzle.selectors.filter.pseudo()→sizzle.selectors.filters,如圖3-1所示。
僞類過濾函數負責檢查元素是否比對僞類,傳回一個布爾值,其參數格式為:
sizzle.selectors.filters[ 僞類 ]( 元素, 序号, 正則比對結果, 元素集合 );
// 正則比對結果是正則
sizzle.selectors.match.pseudo 比對塊選擇器表達式的結果,含有 3 個分組:僞類、引号、僞類參數
相關代碼如下所示,為了友善解釋,代碼中增加了示例和注釋:
4477
filters: {
// $(':enabled') 比對所有可用元素(未禁用的,不隐藏的)
4478
enabled: function( elem ) {
4479 return elem.disabled === false
&& elem.type !== "hidden";
4480
4481
// $(':disabled') 比對所有不可用元素(禁用的)
4482
disabled: function( elem ) {
4483 return elem.disabled === true;
4484
4485
// $(':checked') 比對所有選中的被選中元素,包括複選框、單選按鈕,不包括 option 元素
4486
checked: function( elem ) {
4487 return elem.checked === true;
4488
4489
// $(':selected') 比對所有選中的 option 元素
4490
selected: function( elem ) {
4491 // accessing this property makes
selected-by-default
4492 // options in safari work properly
4493 if ( elem.parentnode ) {
4494 elem.parentnode.selectedindex;
4495 }
4496
4497 return elem.selected === true;
4498
4499
// $(':parent') 比對所有含有子元素或文本的元素
4500
parent: function( elem ) {
4501 return !!elem.firstchild;
4502
4503
// $(':empty') 比對所有不包含子元素或者文本的空元素
4504
empty: function( elem ) {
4505 return !elem.firstchild;
4506
4507
// $(':has(selector)') 比對含有選擇器所比對元素的元素
4508
has: function( elem, i, match ) {
4509 return !!sizzle( match[3], elem
).length;
4510
4511
// $(':header') 比對如 h1、h2、h3 之類的标題元素
4512
header: function( elem ) {
4513 return (/h\d/i).test(
elem.nodename );
4514
4515
// $(':text') 比對所有單行文本框
4516
text: function( elem ) {
4517 var attr = elem.getattribute(
"type" ), type = elem.type;
4518 // ie6 and 7 will map elem.type to
'text' for new html5 types (search, etc)
4519 // use getattribute instead to
test this case
4520 return elem.nodename.tolowercase()
=== "input" && "text" === type && ( attr
=== type || attr === null );
4521
4522
// $(':radio') 比對所有單選按鈕
4523
radio: function( elem ) {
4524
return
elem.nodename.tolowercase() === "input" && "radio"
=== elem.type;
4525
4526
// $(':checkbox') 比對所有複選框
4527
checkbox: function( elem ) {
4528 return elem.nodename.tolowercase()
=== "input" && "checkbox" === elem.type;
4529
4530
// $(':file') 比對所有檔案域
4531
file: function( elem ) {
4532 return elem.nodename.tolowercase()
=== "input" && "file" === elem.type;
4533
4534
// $(':password') 比對所有密碼框
4535
password: function( elem ) {
4536 return elem.nodename.tolowercase()
=== "input" && "password" === elem.type;
4537
4538
// $(':submit') 比對所有送出按鈕
4539
submit: function( elem ) {
4540 var name =
elem.nodename.tolowercase();
4541 return (name === "input"
|| name === "button") && "submit" === elem.type;
4542
4543
// $(':image') 比對所有圖像域
4544
image: function( elem ) {
4545 return elem.nodename.tolowercase()
=== "input" && "image" === elem.type;
4546
4547
// $(':reset') 比對所有重置按鈕
4548
reset: function( elem ) {
4549 var name =
4550 return (name === "input"
|| name === "button") && "reset" === elem.type;
4551
4552
// $(':button') 比對所有按鈕
4553
button: function( elem ) {
4554 var name =
4555 return name === "input"
&& "button" === elem.type || name === "button";
4556
4557
// $(':input') 比對所有 input、textarea、select、button 元素
4558
input: function( elem ) {
4559 return
(/input|select|textarea|button/i).test( elem.nodename );
4560
4561
// $(':focus') 比對目前焦點元素
4562
focus: function( elem ) {
4563 return elem ===
elem.ownerdocument.activeelement;
4564
4565
3.9.6 sizzle.selectors.setfilters
對象sizzle.selectors.setfilters中定義了一組位置僞類和對應的僞類過濾函數,稱為“位置僞類過濾函數集”。支援的位置僞類有::first、:last、:even、:odd、:lt(index)、:gt(index)、:nth(index)、
:eq(index)。方法調用鍊為:sizzle.filter()→sizzle.selectors.filter.pos()→sizzle.selectors.setfilters,如圖3-1所示。
位置僞類過濾函數通過比較下标來确定元素在集合中的位置,傳回一個布爾值,其參數格式為:
sizzle.selectors.setfilters[ 位置僞類 ]( 元素, 下标, 正則比對結果, 元素集合 );
sizzle.selectors.match.pos 比對塊選擇器表達式的結果,含有 2 個分組:位置僞類、位置僞類參數
4566
setfilters: {
// $(':first') 比對找到的第一個元素
4567 first: function( elem, i ) {
4568 return i === 0;
4569 },
4570 // $(':last') 比對找到的最後一個元素
4571 last: function( elem, i, match, array
4572 return i === array.length - 1;
4573 },
4574 // $(':even') 比對所有下标為偶數的元素,從0開始計數
4575 even: function( elem, i ) {
4576 return i % 2 === 0;
4577 },
4578 // $(':odd') 比對所有下标為奇數的元素,從0開始計數
4579 odd: function( elem, i ) {
4580 return i % 2 === 1;
4581 },
4582 // $(':lt(index)') 比對所有小于指定下标的元素
4583 lt: function( elem, i, match ) {
4584 return i < match[3] - 0;
4585 },
4586 // $(':gt(index)') 比對所有大于指定下标的元素
4587 gt: function( elem, i, match ) {
4588 return i > match[3] - 0;
4589 },
4590 // $(':nth(index)') 比對一個指定下标的元素,從
0 開始計數
4591 nth: function( elem, i, match ) {
4592 return match[3] - 0 === i;
4593 },
4594 // $(':eq(index)') 比對一個指定下标的元素,從
4595 eq: function( elem, i, match ) {
4596 return match[3] - 0 === i;
4597 }
4598
3.9.7 sizzle.selectors.filter
對象sizzle.selectors.filter中定義了類型pseudo、child、id、tag、class、attr、pos所對應的過濾函數,稱為“過濾函數集”。方法調用鍊為:sizzle.filter()→sizzle.selectors.filter[ type ],如圖3-1和圖3-6所示。
過濾函數負責檢查元素是否比對過濾表達式,傳回一個布爾值,其參數格式為:
sizzle.selectors.filter[ 類型 ]( 元素, 正則比對結果或過濾表達式,
下标, 元素集合 )
// 正則比對結果指
sizzle.selectors.match 中對應的正則比對塊表達式的結果
// 過濾表達式指經過
sizzle.selectors.prefilter 處理後的塊表達式
sizzle.selectors.filter的總體源碼結構如下所示:
filter: {
pseudo: function( elem, match, i, array ) { ... },
child: function( elem, match ) {
id: function( elem, match ) { ...
tag: function( elem, match ) {
class: function( elem, match ) {
attr: function( elem, match ) {
pos: function( elem, match, i, array )
{ ... }
}
下面對其中的過濾函數逐個進行介紹和分析。
1.?pseudo
僞類過濾函數sizzle.selectors.filter.pseudo( elem, match, i, array )用于檢查元素是否比對僞類。大部分檢查通過調用僞類過濾函數集sizzle.selectors.filters中對應的僞類過濾函數來實作,對于僞類:contains(text)、:not(selector)則做特殊處理。具體請參見3.9.5節。
4599
4600
pseudo: function( elem, match, i, array ) {
4601 var name = match[1],
4602 filter = expr.filters[ name ];
4603
4604 if ( filter ) {
4605 return filter( elem, i, match, array );
4606
4607 } else if ( name ===
"contains" ) {
4608 return (elem.textcontent ||
elem.innertext || gettext([ elem ]) || "").indexof(match[3]) > =
0;
4609
4610 } else if ( name ===
"not" ) {
4611 var not = match[3];
4612
4613 for ( var j = 0, l =
not.length; j < l; j++ ) {
4614 if ( not[j] === elem ) {
4615 return false;
4616 }
4617 }
4618
4619 return true;
4620
4621 } else {
4622 sizzle.error( name );
4623 }
4624
4748
第4600行:參數match是正則sizzle.selectors.match.pseudo比對塊表達式的結果,含有3個分組:僞類、引号、僞類參數。
第4602~4605行:如果在僞類過濾函數集sizzle.selectors.filters中存在對應的僞類過濾函數,則調用它來檢查元素是否比對僞類。
第4607~4608行:僞類:contains(text)用于比對包含指定文本的所有元素。如果僞類是:contains( text ),則先取出目前元素的文本内容,然後調用字元串方法indexof()檢查是否含有指定的文本。
第4610~4619行:僞類:not(selector)用于比對與指定選擇器不比對的所有元素。如果僞類是:not(selector),則檢查目前元素是否與match[3]中的某個元素相等,如果相等則傳回false,否則傳回true。在預過濾函數sizzle.selectors.prefilter.pseudo中,對于僞類:not
(selector),會将match[3]替換為其比對的元素集合。
第4621~4623行:對于不支援的僞類,一律調用方法sizzle.error( msg )抛出文法錯誤。方法sizzle.error( msg )請參見3.10.4節。
2.?child
子元素僞類過濾函數sizzle.selectors.filter.child( elem, match )用于檢查元素是否比對子元素僞類。支援的子元素僞類如表3-14所示。
表3-14 子元素僞類
序 号 子元素僞類 說 明
1 :nth-child(index/even/odd/equation) 比對父元素下的第n個子元素或奇偶元素
2 :first-child 比對父元素的第一個子元素
3 :last-child 比對父元素的最後一個子元素
4 :only-child 如果某個元素是父元素的唯一子元素,則比對;如果父元素還含有多個子元素,則不比對
4626
child: function( elem, match ) {
4627 var first, last,
4628 donename, parent, cache,
4629
count, diff,
4630 type = match[1],
4631 node = elem;
4632
4633 switch ( type ) {
4634 case "only":
4635 case "first":
4636 while ( (node = node.previoussibling)
) {
4637 if ( node.nodetype ===
1 ) {
4638 return false;
4639 }
4640 }
4641
4642 if ( type ===
"first" ) {
4643
return true;
4644 }
4645
4646 node = elem;
4647
4648 case "last":
4649 while ( (node =
node.nextsibling) ) {
4650 if ( node.nodetype ===
4651 return false;
4652 }
4653 }
4654
4655 return true;
4656
4657 case "nth":
4658
first = match[2];
4659 last = match[3];
4660
4661 if ( first === 1
&& last === 0 ) {
4662 return true;
4663 }
4664
4665 donename = match[0];
4666 parent = elem.parentnode;
4667
4668 if ( parent &&
(parent[ expando ] !== donename || !elem.nodeindex) ) {
4669 count = 0;
4670
4671 for ( node =
parent.firstchild; node; node = node.nextsibling ) {
4672 if ( node.nodetype
=== 1 ) {
4673 node.nodeindex
= ++count;
4674 }
4675 }
4676
4677 parent[ expando ] =
donename;
4678 }
4679
4680 diff = elem.nodeindex -
last;
4681
4682 if ( first === 0 ) {
4683 return diff === 0;
4684
4685 } else {
4686 return ( diff % first
=== 0 && diff / first >= 0 );
4687 }
4688 }
4689
第4634~4655行:如果僞類是:only-child,則檢查目前元素之前(previoussibling)和之後(nextsibling)是否有兄弟元素,如果都沒有則傳回true,否則傳回false。注意這裡的分支only是通過分支first和分支last實作的。
第4635~4644行:如果僞類是:first-child,則檢查目前元素之前(previoussibling)是否有兄弟元素,有則傳回false,沒有則傳回true。
第4648~4655行:如果僞類是:last-child,則檢查目前元素之後(nextsibling)是否有兄弟元素,有則傳回false,沒有則傳回true。
第4657~4687行:如果僞類是:nth-child(index/even/odd/equation),則檢查目前元素的下标是否比對僞類參數,檢測公式為:
( 目前元素在其父元素中的下标位置 -
last ) % first === 0
在預過濾函數sizzle.selectors.prefilter.child中已将僞類參數統一格式化為first*n+last,例如,odd格式化為2n+1,其中,first存儲在match[2]中,last存儲在match[3]中。
第4665~4678行:找到目前元素的父元素,然後為每個子元素設定屬性nodeindex,進而辨別出每個子元素的下标位置。如果父元素未被本次過濾辨別過,或目前元素未被辨別過,才會為子元素設定屬性nodeindex,以確定隻會辨別一次。
match[0]是本次過濾的唯一辨別,在執行子元素預過濾函數sizzle.selectors.prefilter.child( match )時被配置設定,具體請參見3.9.4節。
id過濾函數sizzle.selectors.filter.id(
elem, match )用于檢查元素的屬性id是否與指定的id相等。
4691
id: function( elem, match ) {
4692 return elem.nodetype === 1
&& elem.getattribute("id") === match;
4693
标簽過濾函數sizzle.selectors.filter.tag( elem, match )用于檢查元素的标簽名nodename是否與指定的标簽名相等。
5.?class
類樣式過濾函數sizzle.selectors.filter.class( elem, match )用于檢查元素的類樣式classname是否含有指定的類樣式。檢查技巧是在類樣式前後加空格,然後判斷字元串方法indexof()的傳回值。
4221 var expr = sizzle.selectors
= {
4699
class: function( elem, match ) {
4700 return (" " +
(elem.classname || elem.getattribute("class")) + " ")
4701 .indexof( match ) > -1;
4702
6.attr
屬性過濾函數sizzle.selectors.filter.attr( elem, match )用于檢查元素的屬性是否比對屬性表達式。支援的屬性表達式如表3-15所示。
表3-15 屬性表達式
序 号 屬性表達式 說 明
1 [attribute] 比對含有指定屬性的元素
2 [attribute=value] 比對含有指定屬性,并且目前屬性值等于指定值的元素
3 [attribute!=value] 比對不包含指定屬性,或者目前屬性值不等于指定值的元素
4 [attribute^=value] 比對含有指定屬性,并且屬性值以指定值開始的元素
5 [attribute$=value] 比對含有指定屬性,并且目前屬性值以指定值結束的元素
6 [attribute*=value] 比對含有指定屬性,并且目前屬性值包含指定值的元素
7 [attribute|="value"] 比對含有指定屬性,并且目前屬性值等于指定值,或者目前屬性值以指定值開頭,并且後跟一個連字元(-)的元素
8 [attribute~="value"] 比對含有指定屬性,并且目前屬性值含有指定單詞的元素。單詞之間用空格分隔
4704
attr: function( elem, match ) {
4705 var name = match[1],
4706 result = sizzle.attr ?
4707 sizzle.attr( elem, name )
:
4708 expr.attrhandle[ name ] ?
4709 expr.attrhandle[ name ](
elem ) :
4710 elem[ name ] != null ?
4711 elem[ name ] :
4712 elem.getattribute(
name ),
4713 value = result + "",
4714 type = match[2],
4715 check = match[4];
4716
4717 return result == null ?
4718 type === "!=" :
4719 !type && sizzle.attr ?
4720
result != null :
4721 type === "=" ?
4722 value === check :
4723 type === "*=" ?
4724 value.indexof(check) >= 0 :
4725 type === "~=" ?
4726 (" " + value +
" ").indexof(check) >= 0 :
4727 !check ?
4728 value && result !==
false :
4729 type === "!=" ?
4730 value !== check :
4731 type === "^=" ?
4732 value.indexof(check) === 0 :
4733 type === "$=" ?
4734 value.substr(value.length -
check.length) === check :
4735 type === "|=" ?
4736 value === check ||
value.substr(0, check.length + 1) === check + "-" :
4737 false;
4738
第4705行:變量name是指定的屬性名。
第4706~4712行:變量result是元素的html屬性值或dom屬性值。在jquery中,因為sizzle.attr()等價于jquery.attr(),是以總是傳回html屬性,是以變量result也總是html屬性值;在獨立使用sizzle時,則是先嘗試讀取dom屬性值,如果不存在才會讀取html屬性值。
第4713~4715行:變量value是變量result字元串格式;變量type是屬性表達式的等号部分,例如,=、!=;變量check是指定的屬性值。
第4717~4737行:根據等号部分,采用不同的比較方式來檢查元素是否比對屬性表達式。由于這段複合三元表達式太長太複雜,是以,下面将格式稍做調整并加上注釋,以便于閱讀了解:
// [name!=value] 不包含指定屬性
return result == null ? type ===
"!=" :
//
[name] 包含指定屬性
!type && sizzle.attr ? result != null :
[name=check] 包含指定屬性,屬性值等于指定值
type === "=" ? value === check :
[name*=check] 含有指定屬性,屬性值包含指定值
type === "*=" ? value.indexof(check) >= 0 :
[name~="value"] 含有指定屬性,屬性值含有指定單詞
type === "~=" ? (" " + value + "
").indexof(check) >= 0 :
如果沒有指定值 check,隻有指定屬性值,并且屬性值不是 false,才會傳回 true
!check ? value && result !== false :
以下均有指定值 check
[name!=check] 含有指定屬性,屬性值不等于指定值
type === "!=" ? value !== check :
[name^=check] 含有指定屬性,屬性值以指定值開始
type === "^=" ? value.indexof(check) === 0 :
[name$=check] 含有指定屬性,屬性值以指定值結束
type === "$=" ? value.substr(value.length - check.length) ===
check :
[name|=check] 含有指定屬性,屬性值等于指定值,或者以指定值開頭,且後跟一個連字元(-)
type === "|=" ? value === check || value.substr(0,
check.length + 1) === check + "-" :
false;
位置僞類過濾函數sizzle.selectors.filter.pos( elem, match, i, array )用于檢查元素是否比對位置僞類,該函數通過調用位置僞類過濾函數集sizzle.selectors.setfilters中對應的位置僞類過濾函數來實作,具體請參見3.9.6節。調用關系如圖3-1所示。
4740
pos: function( elem, match, i, array ) {
4741 var name = match[2],
4742 filter = expr.setfilters[ name ];
4743
4744 if ( filter ) {
4745 return filter( elem, i, match,
array );
4746 }
4747
4748