天天看点

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

<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 = "&lt;div class='test e'&gt;&lt;/div&gt;&lt;div

class='test'&gt;&lt;/div&gt;";

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"

&amp;&amp; !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" &amp;&amp; !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 &amp;&amp; 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 &lt; 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

&amp;&amp; (" " + elem.classname +

" "). replace(/[\t\n\r]/g, "

").indexof(match) &gt;= 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" &amp;&amp; "2n" ||

                         match[2] ===

"odd" &amp;&amp; "2n+1" ||

4414                     !/\d/.test( match[2] )

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

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

&amp;&amp; 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" &amp;&amp; "text" === type &amp;&amp; ( attr

=== type || attr === null );

4521        

4522        

// $(':radio') 匹配所有单选按钮

4523        

radio: function( elem ) {

4524     

       return

elem.nodename.tolowercase() === "input" &amp;&amp; "radio"

=== elem.type;

4525        

4526        

// $(':checkbox') 匹配所有复选框

4527        

checkbox: function( elem ) {

4528             return elem.nodename.tolowercase()

=== "input" &amp;&amp; "checkbox" === elem.type;

4529        

4530        

// $(':file') 匹配所有文件域

4531        

file: function( elem ) {

4532             return elem.nodename.tolowercase()

=== "input" &amp;&amp; "file" === elem.type;

4533        

4534        

// $(':password') 匹配所有密码框

4535        

password: function( elem ) {

4536             return elem.nodename.tolowercase()

=== "input" &amp;&amp; "password" === elem.type;

4537        

4538        

// $(':submit') 匹配所有提交按钮

4539        

submit: function( elem ) {

4540             var name =

elem.nodename.tolowercase();

4541             return (name === "input"

|| name === "button") &amp;&amp; "submit" === elem.type;

4542        

4543        

// $(':image') 匹配所有图像域

4544        

image: function( elem ) {

4545             return elem.nodename.tolowercase()

=== "input" &amp;&amp; "image" === elem.type;

4546        

4547        

// $(':reset') 匹配所有重置按钮

4548        

reset: function( elem ) {

4549             var name =

4550             return (name === "input"

|| name === "button") &amp;&amp; "reset" === elem.type;

4551        

4552        

// $(':button') 匹配所有按钮

4553        

button: function( elem ) {

4554             var name =

4555             return name === "input"

&amp;&amp; "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 &lt; match[3] - 0;

4585          },

4586          // $(':gt(index)') 匹配所有大于指定下标的元素

4587          gt: function( elem, i, match ) {

4588              return i &gt; 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]) &gt; =

0;

4609

4610             } else if ( name ===

"not" ) {

4611                 var not = match[3];

4612

4613                 for ( var j = 0, l =

not.length; j &lt; 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

&amp;&amp; last === 0 ) {

4662                         return true;

4663                     }

4664                    

4665                     donename = match[0];

4666                     parent = elem.parentnode;

4667    

4668                     if ( parent &amp;&amp;

(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 &amp;&amp; diff / first &gt;= 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

&amp;&amp; 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 ) &gt; -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 &amp;&amp; sizzle.attr ?

4720  

              result != null :

4721                 type === "=" ?

4722                 value === check :

4723                 type === "*=" ?

4724                 value.indexof(check) &gt;= 0 :

4725                 type === "~=" ?

4726                 (" " + value +

" ").indexof(check) &gt;= 0 :

4727                 !check ?

4728                 value &amp;&amp; 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 &amp;&amp; sizzle.attr ? result != null :

[name=check] 包含指定属性,属性值等于指定值

type === "=" ? value === check :

[name*=check] 含有指定属性,属性值包含指定值

type === "*=" ? value.indexof(check) &gt;= 0 :

[name~="value"] 含有指定属性,属性值含有指定单词

type === "~=" ? (" " + value + "

").indexof(check) &gt;= 0 :

如果没有指定值 check,只有指定属性值,并且属性值不是 false,才会返回 true

!check ? value &amp;&amp; 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   

继续阅读