jQuery技术内幕:深入解析jQuery架构设计与实现原理2

    xiaoxiao2024-02-22  112

    第三部分

    底层支持模块

    第3章 选择器Sizzle

    第4章 异步队列Deferred Object

    第5章 数据缓存Data 

    第6章 队列Queue

    第7章 浏览器功能测试Support

    第3章

    选择器Sizzle

    Sizzle是一款纯JavaScript实现的CSS选择器引擎,它具有以下特性:

    完全独立,无库依赖。

    相较于大多数常用选择器其性能非常有竞争力。

    压缩和开启gzip后只有4?KB。

    具有高扩展性和易于使用的API。

    支持多种浏览器,如IE 6.0+、Firefox 3.0+、Chrome 5+、Safari 3+、Opera 9+。

    W3C Selectors API规范定义了方法querySelector()和querySelectorAll(),它们用于根据CSS选择器规范定位文档中的元素,但是老版本的浏览器(如IE 6、IE 7)不支持这两个方法。在Sizzle内部,如果浏览器支持方法querySelectorAll(),则调用该方法查找元素,如果不支持,则模拟该方法的行为。

    Sizzle支持几乎所有的CSS3选择器,并且会按照文档位置返回结果。读者可以访问下面的官网,查看Sizzle的文档和示例:

    http://api.jquery.com/category/selectors/

    http://sizzlejs.com/

    使用jQuery开发时,大多数时候,总是先调用Sizzle查找元素,然后调用jQuery方法对查找结果进行操作。此外,Sizzle也为jQuery事件系统的事件代理提供基础功能(关于事件代理的相关内容请见第9章)。

    本章主要介绍和分析Sizzle的设计思路、工作原理、源码实现(特别是浏览器不支持方法querySelectorAll()的情况),以及jQuery对Sizzle的整合和扩展。

    首先来看看Sizzle的总体源码结构。

    3.1 总体结构

    Sizzle的总体源码结构如代码清单3-1所示,为了方便解释,代码中增加了注释:

    代码清单3-1  Sizzle 的总体源码结构

    (function(){

       // 选择器引擎入口,查找与选择器表达式 selector 匹配的元素集合

       var Sizzle = function( selector, context, results, seed )  { ... };

       // 工具方法,排序、去重

       Sizzle.uniqueSort = function( results )  { ... };

       // 便捷方法,使用指定的选择器表达式 expr 对元素集合 set 进行过滤

       Sizzle.matches = function( expr, set )  { ... };

       // 便捷方法,检查某个元素 node 是否匹配选择器表达式 expr

       Sizzle.matchesSelector = function( node, expr )  { ... };

       // 内部方法,对块表达式进行查找

       Sizzle.find = function( expr, context, isXML )  { ... };

       // 内部方法,用块表达式过滤元素集合

       Sizzle.filter = function( expr, set, inplace, not )  { ... };

       // 工具方法,抛出异常

       Sizzle.error = function( msg )  { ... };

       // 工具方法,获取 DOM 元素集合的文本内容

       var getText = Sizzle.getText = function( elem )  { ... };

       // 扩展方法和属性

       var Expr = Sizzle.selectors = {

           // 块表达式查找顺序

           order: [ "ID", "NAME", "TAG" ],

           // 正则表达式集,用于匹配和解析块表达式

           match:  { ID, CLASS, NAME, ATTR, TAG, CHILD, POS, PSEUDO }, 

           leftMatch:  { ... },

           // 属性名修正函数集

           attrMap: { "class", "for" },

           // 属性值读取函数集

           attrHandle:  { href, type },

           // 块间关系过滤函数集

           relative: { "+", ">", "", "~" },

           // 块表达式查找函数集

           find: { ID, NAME, TAG },

           // 块表达式预过滤函数集

           preFilter: { CLASS, ID, TAG, CHILD, ATTR, PSEUDO, POS },

           // 伪类过滤函数集

           filters: { enabled, disabled, checked, selected, parent, empty, has, header, 

           text, radio, checkbox, file, password, submit, image, reset, button, input, 

           focus },

           // 位置伪类过滤函数集

           setFilters: { first, last, even, odd, lt, gt, nth, eq },

           // 块表达式过滤函数集

           filter: { PSEUDO, CHILD, ID, TAG, CLASS, ATTR, POS }

       };

       // 如果支持方法 querySelectorAll(),则调用该方法查找元素

       if ( document.querySelectorAll ) {

           (function(){

               var oldSizzle = Sizzle;

               Sizzle = function( query, context, extra, seed ) {

                   // 尝试调用方法 querySelectorAll() 查找元素

                   // 如果上下文是 document,则直接调用 querySelectorAll() 查找元素

                   return makeArray( context.querySelectorAll(query), extra );

                   // 如果上下文是元素,则为选择器表达式增加上下文,然后调用 querySelectorAll() 

                   // 查找元素

                   return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + 

                   query ), extra );

                   // 如果查找失败,则仍然调用 oldSizzle()

                   return oldSizzle(query, context, extra, seed);

               };

           })();

       }

       // 如果支持方法 matchesSelector(),则调用该方法检查元素是否匹配选择器表达式

       (function(){

           var matches = html.matchesSelector 

                            || html.mozMatchesSelector 

                            || html.webkitMatchesSelector 

                            || html.msMatchesSelector;

           // 如果支持方法 matchesSelector()

           if ( matches ) {

               Sizzle.matchesSelector = function( node, expr ) {

                   // 尝试调用方法 matchesSelector()

                   var ret = matches.call( node, expr );

                   return ret;

                   // 如果查找失败,则仍然调用 Sizzle()

                   return Sizzle(expr, null, null, [node]).length > 0;

               };

           }

       })();

       // 检测浏览器是否支持 getElementsByClassName()

       (function(){

           Expr.order.splice(1, 0, "CLASS");

           Expr.find.CLASS = function( match, context, isXML )  { ... };

       })();

       // 工具方法,检测元素 a 是否包含元素 b

       Sizzle.contains = function( a, b ) { ... };

    })();

    代码清单3-1中的变量Expr与Sizzle.selectors指向了同一个对象,这么做是为了减少拼写字符数、缩短作用域链,并且方便压缩。但是为了直观和避免混淆,本章在描述中统一使用Sizzle.selectors。

    代码清单3-1中已经介绍了浏览器支持方法querySelectorAll()时的查找过程,本章后面的内容将介绍和分析在不支持的情况下,Sizzle是如何模拟方法querySelectorAll()的行为的。另外,为了简化描述,在后文中把“块表达式查找函数集”“块表达式预过滤函数集”“块表达式过滤函数集”分别简称为“查找函数集”“预过滤函数集”“过滤函数集”。

    代码清单3-1中的方法和属性大致可以分为4类:公开方法、内部方法、工具方法、扩展方法及属性。它们之间的调用关系如图3-1所示。

    图3-1 Sizzle的方法、功能和调用关系

    3.2 选择器表达式

    为了准确描述Sizzle的实现,避免歧义,需要先约定一些相关术语,具体如表3-1所示。

    表3-1 术语和约定

    序号 术  语 说明和示例

    1 选择器表达式 CSS选择器表达式,例如,"div>p"

    2 并列选择器表达式 逗号分割的多个选择器表达式,例如,"div, p"

    3 块表达式 例如,"div>p"中的"div"、"p"

    4 块表达式类型 例如,"div"的类型是TAG,".red"的类型是CLASS,"div.red"则是TAG + CLASS。共有8种块表达式类型:ID、CLASS、NAME、ATTR、TAG、CHILD、POS、PSEUDO

    5 块间关系符 表示块表达式之间关系的符号,例如,"div>p"中的">"。共有4种块间关系符:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素

    选择器表达式由块表达式和块间关系符组成,如图3-2所示。其中,块表达式分为3种:简单表达式、属性表达式、伪类表达式;块间关系符分为4种:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素;块表达式和块间关系符组成了层级表达式。

    图3-2 选择器表达式

    3.3 设计思路

    在正式开始分析Sizzle的源码实现之前,先来讨论和分析下如果要执行一段选择器表达式,或者说设计一个简化版的选择器引擎,需要做些什么工作。下面以"div.red>p"为例来模拟执行过程,具体来说有从左向右查找和从右向左查找两种思路:

    1)从左向右:先查找"div.red"匹配的元素集合,然后查找匹配"p"的子元素集合。

    2)从右向左:先查找"p"匹配的元素集合,然后检查其中每个元素的父元素是否匹配"div.red"。

    无论是从左向右还是从右向左,都必须经历下面3个步骤:

    1)首先要能正确地解析出"div.red>p"中的"div.red"、"p"和">",即解析出选择器表达式中的块表达式和块间关系符。这一步是必需的,否则根本无从下手。

    2)然后要能正确地找到与"div.red"或"p"匹配的元素集合,即查找单个块表达式的匹配元素集合。以"div.red"为例,可以有两种实现方式:

    a.?先查找匹配"div"的元素集合,然后从中过滤出匹配".red"的元素集合。

    b.?先查找匹配".red"的元素集合,然后从中过滤出匹配"div"的元素集合。

    不管采用以上哪种方式,这个过程都可以分解为两个步骤:第一步用块表达式的一部分进行查找,第二步用块表达式的剩余部分对查找的结果进行过滤。

    3)最后来处理"div.red"和"p"之间的关系符">",即处理块表达式之间的父子关系。在这一步骤中,从左向右和从右向左的处理方式是截然不同的:

    a.?从左向右:找到"div.red"匹配的元素集合的子元素集合,然后从中过滤出匹配"p"的子元素集合。

    b.?从右向左:检查每个匹配"p"的元素的父元素是否匹配"div.red",只保留匹配的元素。

    无论采用以上哪种方式,这个过程都可以分解为两个步骤:第一步按照块间关系符查找元素,第二步用块表达式对查找的结果进行过滤。不论元素之间是哪种关系(父子关系、祖先后代关系、相邻的兄弟关系或不相邻的兄弟关系),都可以采用这种方式来查找和过滤。

    另外,如果还有更多的块表达式,则重复执行第3步。

    对于前面的3个步骤,可以进一步提炼总结,如下:

    1)处理选择器表达式:解析选择器表达式中的块表达式和块间关系符。

    2)处理块表达式:用块表达式的一部分查找,用剩余部分对查找结果进行过滤。

    3)处理块间关系符:按照块间关系符查找,用块表达式对查找结果进行过滤。

    从前面对选择器表达式的执行过程的分析,还可以推导分析出以下结论:

    从左向右的总体思路是不断缩小上下文,即不断缩小查找范围。

    从右向左的总体思路是先查找后过滤。

    在从左向右的查找过程中,每次处理块间关系符时都需要处理未知数量的子元素或后代元素,而在从右向左的查找过程中,处理块间关系符时只需要处理单个父元素或有限数量的祖先元素。因此,在大多数情况下,采用从右向左的查找方式其效果要高于从左向右。

    在了解了两种执行思路后,现在再来看看Sizzle,它是一款从右向左查找的选择器引擎,提供了与前面3个步骤相对应的核心接口:

    正则chunker负责从选择器表达式中提取块表达式和块间关系符。

    方法Sizzle.find( expr, context, isXML )负责查找块表达式匹配的元素集合,方法Sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。

    对象Sizzle.selector.relative中的块间关系过滤函数根据块间关系符过滤元素集合。

    函数Sizzle( selector, context, results, seed )则按照前面3个步骤将这些核心接口组织起来。

    本节对选择器引擎和Sizzle的设计思路作了探索和概述,接下来看看Sizzle的源码实现。

    3.4 Sizzle( selector, 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, context, results, seed )

    相关代码如下所示:

    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     } else {

    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         } else {

    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

    相关代码如下所示:

    3949         if ( context ) {

    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     if ( !checkSet ) {

    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         } else {

    4007             for ( i = 0; checkSet[i] != null; i++ ) {

    4008                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {

    4009                     results.push( set[i] );

    4010                 }

    4011             }

    4012         }

    4013 

    4014     } else {

    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     return results;

    4024 };

    函数Sizzle( selector, context, results, seed )的执行过程可以总结为图3-3。

    3.5 正则chunker

    正则chunker用于从选择器表达式中提取块表达式和块间关系符。该正则是Sizzle中最长、最复杂和最关键的正则,图3-4是该正则的分解图,图中包含了每个子块的功能介绍和测试用例。

    3.6 Sizzle.find( expr, context, isXML )

    方法Sizzle.find( expr, context, isXML )负责查找与块表达式匹配的元素集合。该方法会按照表达式类型数组Sizzle.selectors.order规定的查找顺序(ID、CLASS、NAME、TAG)逐个尝试查找,如果未找到,则查找上下文的所有后代元素(*)。

    图3-3 Sizzle( selector, context, results, seed )的执行过程

    方法Sizzle.find( expr, context, isXML )执行的5个关键步骤如下:

    1)先用正则集合Sizzle.selectors.leftMatch中的正则确定表达式类型。

    2)然后调用查找函数集Sizzle.selectors.find中对应类型的查找函数,查找匹配的元素集合。

    3)然后删除块表达式中已查找过的部分。

    4)如果没有找到对应类型的查找函数,则读取上下文的所有后代元素。

    5)最后返回格式为{set:候选集, expr:块表达式剩余部分}的对象。

    下面来看看该方法的源码实现。

    1.?定义Sizzle.find( expr, context, isXML )

    相关代码如下所示:

    4051 Sizzle.find = function( expr, context, isXML ) {

    第4051行:定义方法Sizzle.find( expr, context, isXML ),它接受3个参数:

    参数expr:块表达式。

    参数context:DOM元素或文档对象,作为查找时的上下文。

    参数isXML:布尔值,指示是否运行在一个XML文档中。

    2.?遍历表达式类型数组Sizzle.selectors.order

    相关代码如下所示:

    4052     var set, i, len, match, type, left;

    4053 

    4054     if ( !expr ) {

    4055         return [];

    4056     }

    4057 

    4058     for ( i = 0, len = Expr.order.length; i < len; i++ ) {

    4059         type = Expr.order[i];

    4060         

    第4054~4056行:如果块表达式expr是空字符串,则直接返回空数组[]。

    第4058行:表达式类型数组Sizzle.selectors.order中定义了查找单个块表达式时的顺序,依次是ID、CLASS、NAME、TAG,其中,CLASS需要浏览器支持方法getElements

    ByClassName()。关于Sizzle.selectors.order的具体说明请参见3.9.1节。

    (1)确定块表达式类型Sizzle.selectors.leftMatch[ type ]

    相关代码如下所示:

    4061         if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {

    4062             left = match[1];

    4063             match.splice( 1, 1 );

    4064 

    第4061行:检查每个表达式类型type在Sizzle.selectors.leftMatch中对应的正则是否匹配块表达式expr,如果匹配,则可以确定块表达式的类型。

    第4062~4063行:对象Sizzle.selectors.leftMatch中存放了表达式类型和正则的映射,正则可以用于确定块表达式的类型,并解析其中的参数。它是基于对象Sizzle.selectors.match初始化的,具体请参见3.9.2节。

    (2)查找匹配元素Sizzle.selectors.find[ type ]

    相关代码如下所示:

    4065             if ( left.substr( left.length - 1 ) !== "\\" ) {

    4066                 match[1] = (match[1] || "").replace( rBackslash, "" );

    4067                 set = Expr.find[ type ]( match, context, isXML );

    4068

    第4065行:如果匹配正则的内容以反斜杠"\\"开头,表示反斜杠"\\"之后的字符被转义了,不是期望的类型,这时会认为类型匹配失败。

    第4066行:过滤其中的反斜杠,以支持将某些特殊字符(例如,“#”、“.”、“[”)作为普通字符使用。举个例子,假设某个input元素的属性id是"a.b",则对应的选择器表达式应该写作$("#a\\.b"),这里替换掉了反斜杠,又会变回"a.b",因此仍然可以通过执行document.getElementsById("a.b")查找到input元素。

    第4067行:调用表达式类型type在查找函数集Sizzle.selectors.find中对应的查找函数,查找匹配的元素集合。Sizzle.selectors.find中定义了ID、CLASS、NAME、TAG所对应的查找函数,具体请参见3.9.3节。

    (3)删除块表达式中已查找过的部分

    相关代码如下所示:

    4069                 if ( set != null ) {

    4070                     expr = expr.replace( Expr.match[ type ], "" );

    4071                     break;

    4072                 }

    4073             }

    4074         }

    4075     }

    4076 

    第4069~4072行:如果set不是null和undefined,表示对应的查找函数执行了查找操作,则不管有没有找到匹配元素,都将块表达式expr中已查找过的部分删除,并结束方法Sizzle.find( expr, context, isXML )的查找过程。

    第4070行:用对象Sizzle.selectors.match中对应的正则来匹配已查找过的部分,具体请参见3.9.2节。

    3.?如果没有找到对应类型的查找函数,则读取上下文的所有后代元素

    相关代码如下所示:

    4077     if ( !set ) {

    4078         set = typeof context.getElementsByTagName !== "undefined" ?

    4079             context.getElementsByTagName( "*" ) :

    4080             [];

    4081     }

    4082 

    第4077~4081行:如果set是null或undefined,(大多数情况下)表示没有在Sizzle.selectors.find中找到对应的查找函数,例如,$(":input")会读取上下文的所有后代元素作为候选集。

    4.?返回{ set:候选集, expr:块表达式剩余部分}

    相关代码如下所示:

    4083     return { set: set, expr: expr };

    4084 };

    方法Sizzle.find( expr, context, isXML )的执行过程可总结为图3-5。

    图3-5 Sizzle.find( expr, context, isXML )的执行过程

    3.7 Sizzle.filter( expr, set, inplace, not )

    方法Sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。在该方法内部,将用过滤函数集Sizzle.selectors.filter中的过滤函数来执行过滤操作。

    方法Sizzle.filter( expr, set, inplace, not )实现的5个关键步骤如下:

    1)首先用正则集合Sizzle.selectors.leftMatch中的正则确定块表达式类型。

    2)然后调用预过滤函数集Sizzle.selectors.preFilter中对应类型的预过滤函数,执行过滤前的修正操作。

    3)调用过滤函数集Sizzle.selectors.filter[ type ]中对应类型的过滤函数,执行过滤操作,如果过滤函数返回false,则把元素集合中对应位置的元素替换为false。

    4)最后删除块表达式中已过滤的部分。

    5)重复第1)~4)步,直至块表达式变为空字符串。

    下面来看看该方法的源码实现。

    1.定义Sizzle.filter( expr, set, inplace, not )

    相关代码如下所示:

    4086 Sizzle.filter = function( expr, set, inplace, not ) {

    第4086行:定义方法Sizzle.filter( expr, set, inplace, not ),它接受 4 个参数:

    参数expr:块表达式。

    参数set:待过滤的元素集合。

    参数inplace:布尔值。如果为true,则将元素集合set中与选择器表达式不匹配的元素设置为false;如果不为true,则重新构造一个元素数组并返回,只保留匹配元素。

    参数not:布尔值。如果为true,则去除匹配元素,保留不匹配元素;如果不为true,则去除不匹配元素,保留匹配元素。

    2.?用块表达式expr过滤元素集合set,直到expr为空

    相关代码如下所示:

    4087     var match, anyFound,

    4088         type, found, item, filter, left,

    4089         i, pass,

    4090         old = expr,

    4091         result = [],

    4092         curLoop = set,

    4093         isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

    4094 

    4095     while ( expr && set.length ) {

    第4095行:用块表达式expr过滤元素集合set,直到expr变为空字符串。如果候选集set变为空数组,则没有必要继续执行过滤操作。

    (1)遍历块过滤函数集Sizzle.selectors.filter

    相关代码如下所示:

    4096         for ( type in Expr.filter ) {

    第4096行:遍历块过滤函数集Sizzle.selectors.filter,调用其中的过滤函数执行过滤操作。块过滤函数集Sizzle.selectors.filter中定义了PSEUDO、CHILD、ID、TAG、CLASS、ATTR、POS对应的过滤函数,具体请参见3.9.7节。

    (2)确定块表达式类型Sizzle.selectors.leftMatch[ type ]

    相关代码如下所示:

    4097             if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {

    4098                 filter = Expr.filter[ type ];

    4099                 left = match[1];

    4100 

    4101                 anyFound = false;

    4102 

    4103                 match.splice(1,1);

    4104 

    4105                 if ( left.substr( left.length - 1 ) === "\\" ) {

    4106                     continue;

    4107                 }

    4108

    第4097行:检查每个表达式类型type在Sizzle.selectors.leftMatch中对应的正则是否匹配块表达式expr,如果匹配,则可以确定块表达式的类型。

    第4105~4107行:如果匹配正则的内容以反斜杠"\\"开头,表示反斜杠"\\"之后的字符被转义了,不是期望的类型,这时会认为类型匹配失败。

    (3)调用预过滤函数Sizzle.selectors.preFilter[ type ]

    相关代码如下所示:

    4109                 if ( curLoop === result ) {

    4110                     result = [];

    4111                 }

    4112 

    4113                 if ( Expr.preFilter[ type ] ) {

    4114                     match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

    4115 

    4116                     if ( !match ) {

    4117                        anyFound = found = true;

    4118 

    4119                     } else if ( match === true ) {

    4120                        continue;

    4121                     }

    4122                 }

    4123

    第4109~4111行:用于缩小候选集。

    第4113~4122行:如果在预过滤函数集Sizzle.selectors.preFilter中存在对应的预过滤函数,则调用,执行过滤前的修正操作。预过滤函数负责进一步修正过滤参数,具体请参见3.9.4节。

    第4116~4121行:预过滤函数有3种返回值:

    false:已经执行了过滤,缩小了候选集,例如,CLASS。

    true:需要继续执行预过滤,尚不到执行过滤函数的时候,例如,POS、CHILD。

    字符串:修正后的过滤参数(通常是块表达式),后面会继续调用对应的过滤函数。

    (4)调用过滤函数Sizzle.selectors.filter[ type ]

    相关代码如下所示:

    4124                 if ( match ) {

    4125                     for ( i = 0; (item = curLoop[i]) != null; i++ ) {

    4126                         if ( item ) {

    4127                             found = filter( item, match, i, curLoop );

    4128                             pass = not ^ found;

    4129 

    4130                             if ( inplace && found != null ) {

    4131                                 if ( pass ) {

    4132                                     anyFound = true;

    4133 

    4134                                 } else {

    4135                                     curLoop[i] = false;

    4136                                 }

    4137 

    4138                             } else if ( pass ) {

    4139                                 result.push( item );

    4140                                 anyFound = true;

    4141                             }

    4142                         }

    4143                     }

    4144                 }

    4145

    第4124~4144行:遍历元素集合curLoop,对其中的每个元素执行过滤函数,检测元素是否匹配。

    第4127~4128行:变量found表示当前元素是否匹配过滤表达式;变量pass表示当前元素item是否可以通过过滤表达式的过滤。如果变量found为true,表示匹配,此时如果未指定参数not,则变量pass为true;如果变量found为false,表示不匹配,此时如果参数not为true,则变量pass为true;其他情况下,变量pass为false。

    第4130~4141行:如果参数inplace为true,则将与块表达式expr不匹配的元素设置为false;如果参数inplace不是true,则重新构造一个元素数组,只保留匹配元素,即会不断地缩小元素集合。

    (5)删除块表达式expr中已过滤的部分

    相关代码如下所示:

    4146                 if ( found !== undefined ) {

    4147                     if ( !inplace ) {

    4148                         curLoop = result;

    4149                     }

    4150 

    4151                     expr = expr.replace( Expr.match[ type ], "" );

    4152 

    4153                     if ( !anyFound ) {

    4154                         return [];

    4155                     }

    4156 

    4157                     break;

    4158                 }

    4159             }

    4160         }

    4161

    第4146行:变量found是过滤函数Sizzle.selectors.filter[ type ]的返回值,如果不等于undefined,表示至少执行过一次过滤。大多数情况下,过滤操作发生在过滤函数中,不过也可能发生在预过滤函数中,例如,CLASS、POS、CHILD。

    第4147~4149行:如果参数inplace不是true,则将新构建的元素数组赋值给变量curLoop,在下次循环时,会将result再次置为空数组(见第4109~4111行),然后存放通过过滤的元素(见第4130~4141行),然后再赋值给变量curLoop,即会不断地缩小元素集合。

    第4151行:删除块表达式中已过滤过的部分,直至块表达式变为空字符串。用对象Sizzle.selectors.match中对应的正则匹配已过滤过的部分,具体请参见3.9.2节。

    第4153~4155行:如果没有找到可以通过过滤的元素,直接返回一个空数组。

    (6)如果块表达式没有发生变化,则认为不合法

    相关代码如下所示:

    4162         // Improper expression

    4163         if ( expr === old ) {

    4164             if ( anyFound == null ) {

    4165                 Sizzle.error( expr );

    4166 

    4167             } else {

    4168                 break;

    4169             }

    4170         }

    4171 

    4172         old = expr;

    4173     }

    4174

    4178 Sizzle.error = function( msg ) {

    4179     throw new Error( "Syntax error, unrecognized expression: " + msg );

    4180 };

    第4163~4172行:如果块表达式expr没有发生变化,说明前面的过滤没有生效,动不了块表达式expr分毫,此时如果没有找到可以通过过滤的元素,则认为块表达式expr不合法,抛出语法错误的异常。

    3.?返回过滤后的元素集合,或缩减范围后的元素集合

    相关代码如下所示:

    4175     return curLoop;

    4176 };

    方法Sizzle.filter( expr, set, inplace, not )的执行过程可以总结为图3-6。

    3.8 Sizzle.selectors.relative

    对象Sizzle.selectors.relative中存放了块间关系符和对应的块间关系过滤函数,称为“块间关系过滤函数集”。

    块间关系符共有4种,其含义和过滤方式如表3-2所示。

    图3-6 Sizzle.filter( expr, set, inplace, not )的执行过程

    表3-2 块间关系符的含义和过滤方式

    序号 块间关系符 选择器表达式 说  明 从右向左的过滤方式

    1 "" ancestor descendant 匹配所有后代元素 检查祖先元素是否匹配左侧的块表达式

    2 "+" prev + next 匹配下一个兄弟元素 检查前一个兄弟元素否匹配左侧的块表达式

    3 ">" parent > child 匹配所有子元素 检查父元素是否匹配左侧的块表达式

    4 "~" prev~siblings 匹配之后的所有兄弟元素 检查之前的兄弟元素是否匹配左侧的块表达式

    在函数Sizzle( selector, context, results, seed )从右向左进行过滤时,块间关系过滤函数被调用,用于检查映射集checkSet中的元素是否匹配块间关系符左侧的块表达式。调用时的参数格式为:

    Sizzle.selectors.relative[ 块间关系符 cur ]( 映射集 checkSet, 左侧块表达式 pop, contextXML );

    块间关系过滤函数接受3个参数:

    参数checkSet:映射集,对该元素集合执行过滤操作。

    参数part:大多数情况下是块间关系符左侧的块表达式,该参数也可以是DOM元素。

    参数isXML:布尔值,指示是否运行在一个XML文档中。

    块间关系过滤函数实现的3个关键步骤如下:

    1)遍历映射集checkSet。

    2)按照块间关系符查找每个元素的兄弟元素、父元素或祖先元素。

    3)检查找到的元素是否匹配参数part,并替换映射集checkSet中对应位置的元素。

    a.?如果参数part是标签,则检查找到的元素其节点名称nodeName是否与之相等,如果相等则替换为找到的元素,不相等则替换为false。

    b.?如果参数part是DOM元素,则检查找到的元素是否与之相等,如果相等则替换为true,不相等则替换为false。

    c.?如果参数part是非标签字符串,则调用方法Sizzle.filter( selector, set, inplace, not )过滤。

    也就是说,遍历结束后,映射集checkSet中的元素可能会是兄弟元素、父元素、祖先元素、true或false。

    3.8.1 "+"

    块间关系符"+"匹配选择器"prev + next",即匹配所有紧接在元素prev后的兄弟元素next。例如,$("div + span")、$(".lastdiv + span")。对于从右向左的查找方式,则是检查元素next之前的兄弟元素是否匹配块表达式prev。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4251     relative: {

    4252         "+": function(checkSet, part){

    4253             var isPartStr = typeof part === "string",

    4254                 isTag = isPartStr && !rNonWord.test( part ),

    4255                 isPartStrNotTag = isPartStr && !isTag;

    4256 

    4257             if ( isTag ) {

    4258                 part = part.toLowerCase();

    4259             }

    4260 

    4261             for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {

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

    4263                     while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

    4264 

    4265                     checkSet[i] = isPartStrNotTag || elem && elem.node

    Name.toLowerCase() === part ?

    4266                         elem || false :

    4267                         elem === part;

    4268                 }

    4269             }

    4270 

    4271             if ( isPartStrNotTag ) {

    4272                 Sizzle.filter( part, checkSet, true );

    4273             }

    4274         },

    4338     },

    4749 };

    第4253~4255行:定义一组局部变量,它们的含义和用途如下:

    变量isPartStr:指示参数part是否是字符串。

    变量isTag:指示参数part是否为标签字符串。

    变量isPartStrNotTag:指示参数part是否是非标签字符串。

    第4261~4269行:遍历映射集checkSet,查找每个元素的前一个兄弟元素,并替换映射集checkSet中对应位置的元素,有以下3个逻辑分支:

    如果未找到兄弟元素,则替换为false。

    如果找到了兄弟元素,并且参数part是标签,则检查兄弟元素的节点名称nodeName是否与之相等,如果相等则替换为兄弟元素,不相等则替换为false。

    如果找到了兄弟元素,并且参数part是DOM元素,则检查二者是否相等,如果相等则替换为true,不相等则替换为false。

    因此,在遍历结束后,映射集checkSet中的元素可能会是兄弟元素、true或false。

    第4263行:在遍历兄弟元素的同时过滤掉非元素节点,并且只要取到一个兄弟元素就退出while循环。

    第4271~4273行:如果参数part是非标签字符串,则调用方法Sizzle.filter( selector, set, inplace, not )过滤映射集checkSet。对于参数part是标签和DOM元素的情况,在前面遍历映射集checkSet时已经处理过了。

    3.8.2 ">"

    块间关系符">"用于选择器"parent > child",即匹配父元素parent下的子元素child。例如,$("div + span")、$(".lastdiv + span")。对于从右向左的查找方式,则是检查子元素child的父元素是否匹配块表达式parent。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4276         ">": function( checkSet, part ) {

    4277             var elem,

    4278                 isPartStr = typeof part === "string",

    4279                 i = 0,

    4280                 l = checkSet.length;

    4281 

    4282             if ( isPartStr && !rNonWord.test( part ) ) {

    4283                 part = part.toLowerCase();

    4284 

    4285                 for ( ; i < l; i++ ) {

    4286                     elem = checkSet[i];

    4287 

    4288                     if ( elem ) {

    4289                         var parent = elem.parentNode;

    4290                         checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;

    4291                     }

    4292                 }

    4293 

    4294             } else {

    4295                 for ( ; i < l; i++ ) {

    4296                     elem = checkSet[i];

    4297 

    4298                     if ( elem ) {

    4299                         checkSet[i] = isPartStr ?

    4300                            elem.parentNode :

    4301                            elem.parentNode === part;

    4302                     }

    4303                 }

    4304 

    4305                 if ( isPartStr ) {

    4306                     Sizzle.filter( part, checkSet, true );

    4307                 }

    4308             }

    4309         },

    4338     },

    4749 };

    第4282~4292行:如果参数part是标签,则遍历映射集checkSet,查找每个元素的父元素,并检查父元素的节点名称nodeName是否与参数part相等,如果相等则替换映射集checkSet中对应位置的元素为父元素,不相等则替换为false。

    第4294~4307行:如果参数part不是标签,则可能是非标签字符串或DOM元素,同样遍历映射集checkSet,查找每个元素的父元素,并替换映射集checkSet中对应位置的元素,在这个过程中有以下2个逻辑分支:

    如果参数part是非标签字符串,则在遍历映射集checkSet的过程中,替换映射集checkSet中对应位置的元素为父元素,遍历结束后调用方法Sizzle.filter( selector, set, inplace, not )过滤映射集checkSet。

    如果参数part是元素,则在遍历映射集checkSet时,检查每个元素的父元素是否与之相等,如果相等则替换映射集checkSet中对应位置的元素为true,不相等则替换为false。

    因此,在遍历结束后,映射集checkSet中的元素可能会是父亲元素、true或false。

    3.8.3 ""

    块间关系符""用于选择器"ancestor descendant",即匹配祖先元素ancestor的所有后代元素descendant。例如,$("div button")、$("div .btn")。对于从右向左的查找方式,则是检查后代元素descendant的祖先元素是否匹配块表达式ancestor。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4311         "": function(checkSet, part, isXML){

    4312             var nodeCheck,

    4313                 doneName = done++,

    4314                 checkFn = dirCheck;

    4315 

    4316             if ( typeof part === "string" && !rNonWord.test( part ) ) {

    4317                 part = part.toLowerCase();

    4318                 nodeCheck = part;

    4319                 checkFn = dirNodeCheck;

    4320             }

    4321 

    4322             checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );

    4323         },

    4338     },

    4749 };

    第4312~4322行:这段代码含有2个逻辑分支:

    如果参数part是非标签字符串或DOM元素,则调用函数dirCheck()过滤映射集checkSet。

    如果参数part是标签,则调用函数dirNodeCheck()过滤映射集checkSet。

    调用函数dirCheck()和dirNodeCheck()时的参数格式为:

    checkFn( 方向 "parentNode/previousSibling", 块表达式 part, 缓存计数器 doneName, 映射集 checkSet, nodeCheck, isXML )

    函数dirCheck()和dirNodeCheck()会遍历映射集checkSet,查找每个元素的祖先元素,并检查是否有祖先元素匹配参数part,同时替换映射集checkSet中对应位置的元素。具体请参见3.8.5节和3.8.6节。

    3.8.4 "~"

    块间关系符"~"用于选择器"prev~siblings",即匹配元素prev之后的所有兄弟元素siblings。例如,$('div~p')。对于从右向左的查找方式,则是检查元素siblings之前的兄弟元素是否匹配块表达式prev。

    Sizzle.selectors.relative["~"]( checkSet, part )的源码实现与Sizzle.selectors.relative[""]( checkSet, part )几乎一样,两者的区别仅仅在于调用函数dirCheck()和dirNodeCheck()时第一个参数的值不同,前者是"previousSibling",后者则是"parentNode"。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4325         "~": function( checkSet, part, isXML ) {

    4326             var nodeCheck,

    4327                 doneName = done++,

    4328                 checkFn = dirCheck;

    4329 

    4330             if ( typeof part === "string" && !rNonWord.test( part ) ) {

    4331                 part = part.toLowerCase();

    4332                 nodeCheck = part;

    4333                 checkFn = dirNodeCheck;

    4334             }

    4335 

    4336             checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );

    4337         }

    4338     },

    3.8.5 dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML )

    函数dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML )负责遍历候选集checkSet,检查其中每个元素在某个方向dir上是否有与参数cur匹配或相等的元素。如果找到,则将候选集checkSet中对应位置的元素替换为找到的元素或true;如果未找到,则替换为false。

    在块间关系过滤函数Sizzle.selectors.relative[""/"~"]( checkSet, part )中,当参数part是非标签字符串或DOM元素时,才会调用函数dirCheck()。

    相关代码如下所示:

    5201 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {

    5202     for ( var i = 0, l = checkSet.length; i < l; i++ ) {

    5203         var elem = checkSet[i];

    5204 

    5205         if ( elem ) {

    5206             var match = false;

    5207             

    5208             elem = elem[dir];

    5209 

    5210             while ( elem ) {

    5211                 if ( elem[ expando ] === doneName ) {

    5212                     match = checkSet[elem.sizset];

    5213                     break;

    5214                 }

    5215 

    5216                 if ( elem.nodeType === 1 ) {

    5217                     if ( !isXML ) {

    5218                         elem[ expando ] = doneName;

    5219                         elem.sizset = i;

    5220                     }

    5221 

    5222                     if ( typeof cur !== "string" ) {

    5223                         if ( elem === cur ) {

    5224                             match = true;

    5225                             break;

    5226                         }

    5227 

    5228                     } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {

    5229                         match = elem;

    5230                         break;

    5231                     }

    5232                 }

    5233 

    5234                 elem = elem[dir];

    5235             }

    5236 

    5237             checkSet[i] = match;

    5238         }

    5239     }

    5240 }

    第5201行:定义函数dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ),它接受6个参数:

    参数dir:表示查找方向的字符串,例如,“parentNode”、“previousSibling”。

    参数cur:大多数情况下是非标签字符串格式的块表达式,也可能是DOM元素。

    参数doneName:数值。本次查找的唯一标识,用于优化查找过程,避免重复查找。

    参数checkSet:候选集,在查找过程中,其中的元素将被替换为父元素、祖先元素、兄弟元素、true或false。

    参数nodeCheck:undefined。在后面的代码中没有用到该参数。

    参数isXML:布尔值,指示是否运行在一个XML文档中。

    第5202~5239行:遍历候选集checkSet,对其中的每个元素,沿着某个方向(例如,“parentNode”、“previousSibling”)一直查找,直到找到与参数cur(DOM元素)相等或者与参数cur(非标签字符串)匹配的元素为止,或者直到在该方向上不再有元素为止。

    如果找到与参数cur(DOM元素)相等的元素,则替换映射集checkSet中对应位置的元素为true;如果找到与参数cur(非标签字符串)匹配的元素,则替换为找到的元素;如果未找到,则默认替换为false。

    第5211~5220行:在查找过程中,如果遇到已经检查过的元素,则直接取该元素在候选集checkSet中对应位置上的元素,避免重复查找。

    第5222~5231行:如果参数cur是DOM元素,则直接检查找到的元素是否与之相等;如果参数cur是非标签字符串,则调用方法Sizzle.filter( expr, set, inplace, not )检查是否与之匹配。

    第5237行:替换映射集checkSet中对应位置的元素。变量match的初始值为false;如果找到与参数cur(DOM元素)相等的元素,则其值变为true;如果找到与参数cur(非标签字符串)匹配的元素,则其值变为找到的元素。

    3.8.6 dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML )

    函数dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML )负责遍历候选集checkSet,检查其中每个元素在某个方向dir上是否有与参数cur匹配的元素。如果找到,则将候选集checkSet中对应位置的元素替换为找到的元素;如果未找到,则替换为false。

    在块间关系过滤函数Sizzle.selectors.relative[""/"~"]( checkSet, part )中,当参数part是标签时,才会调用函数dirNodeCheck()。

    相关代码如下所示:

    5168 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {

    5169     for ( var i = 0, l = checkSet.length; i < l; i++ ) {

    5170         var elem = checkSet[i];

    5171 

    5172         if ( elem ) {

    5173             var match = false;

    5174 

    5175             elem = elem[dir];

    5176 

    5177             while ( elem ) {

    5178                 if ( elem[ expando ] === doneName ) {

    5179                     match = checkSet[elem.sizset];

    5180                     break;

    5181                 }

    5182 

    5183                 if ( elem.nodeType === 1 && !isXML ){

    5184                     elem[ expando ] = doneName;

    5185                     elem.sizset = i;

    5186                 }

    5187 

    5188                 if ( elem.nodeName.toLowerCase() === cur ) {

    5189                     match = elem;

    5190                     break;

    5191                 }

    5192 

    5193                 elem = elem[dir];

    5194             }

    5195 

    5196             checkSet[i] = match;

    5197         }

    5198     }

    5199 }

    第5168行:定义函数dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ),它接受6个参数:

    参数dir:表示查找方向的字符串,例如,“parentNode”、“previousSibling”。

    参数cur:标签字符串。

    参数doneName:数值。本次查找的唯一标识,用于优化查找过程,避免重复查找。

    参数checkSet:候选集,在查找过程中,其中的元素将被替换为与参数cur匹配的元素或false。

    参数nodeCheck:标签字符串。在后边的代码中没有用到该参数。

    参数isXML:布尔值,指示是否运行在一个XML文档中。

    第5169~5198行:遍历候选集checkSet,对其中的每个元素,沿着某个方向(例如,“parentNode”、“previousSibling”)一直查找,直到找到节点名称nodeName与参数cur相等的元素,或者在该方向上不再有元素为止。如果找到,则替换映射集checkSet中对应位置的元素为找到的元素;如果未找到,则默认替换为false。

    第5178~5186行:在查找过程中,如果遇到已经检查过的元素,则直接取该元素在候选集checkSet中对应位置上的元素,避免重复查找。

    第5196行:替换映射集checkSet中对应位置的元素。变量match初始值为false,如果找到节点名称nodeName与参数cur相等的元素,则其值变为找到的元素。

    3.9 Sizzle.selectors

    对象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         return;

    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", "台北Ta?ibe?i"]

    4 CLASS.exec(".foo\\:bar") [".foo\\:bar", "foo\\:bar"]

    5 CLASS.exec(".test\\.foo\\[5\\]bar") [".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"]", "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']", "rel", "=", "'", "bookmark", undefined]

    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", undefined]

    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", undefined, undefined, undefined, undefined]

    图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", undefined]

    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", "1"]

    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", undefined]

    6 POS.exec("p:last") [":last", "last", undefined]

    7 POS.exec("p:even") [":even", "even", undefined]

    8 POS.exec("p:odd") [":odd", "odd", undefined]

    图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", undefined, undefined]

    5 PSEUDO.exec(":radio") [":radio", "radio", undefined, undefined]

    6 PSEUDO.exec(":checkbox") [":checkbox", "checkbox", undefined, undefined]

    7 PSEUDO.exec(":text") [":text", "text", undefined, undefined]

    8 PSEUDO.exec(":radio:checked") [":radio", "radio", undefined, undefined]

    9 PSEUDO.exec(":checkbox:checked") [":checkbox", "checkbox", undefined, undefined]

    10 PSEUDO.exec("option:selected") [":selected", "selected", undefined, undefined]

    11 PSEUDO.exec(":header") [":header", "header", undefined, undefined]

    12 PSEUDO.exec(":empty") [":empty", "empty", undefined, undefined]

    13 PSEUDO.exec(":parent") [":parent", "parent", undefined, undefined]

    14 PSEUDO.exec(":hidden") [":hidden", "hidden", undefined, undefined]

    15 PSEUDO.exec(":visible") [":visible", "visible", undefined, undefined] 

    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

    相关代码如下所示: 

    4221 var Expr = Sizzle.selectors = {

    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     },

    4749 };

    2.?CLASS

    相关代码如下所示:

    5139 (function(){

             // 测试浏览器是否支持方法 getElementsByClassName()

             // 如果不支持,则不做任何事情

             // 如果当前浏览器支持方法 getElementsByClassName()

    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     };

    5166 })();

    3.?NAME

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4340     find: {

    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

    4370     },

    4749 };

    4.?TAG

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4340     find: {

    4365         TAG: function( match, context ) {

    4366             if ( typeof context.getElementsByTagName !== "undefined" ) {

    4367                 return context.getElementsByTagName( match[1] );

    4368             }

    4369         }

    4370     },

    4749 };

    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,

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    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     },

    4749 };

    第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节。

    相关代码如下所示:

    3866     rBackslash = /\\/g,

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    4395         ID: function( match ) {

    4396             return match[1].replace( rBackslash, "" );

    4397         },

    4475     },

    4749 };

    3.?TAG

    标签预过滤函数Sizzle.selectors.preFilter.TAG( match, curLoop )负责过滤转义反斜杠,转换为小写,从匹配结果match中提取并返回标签名。关于正则Sizzle.selectors.match/left

    Match.TAG的具体说明参见3.9.2节。

    相关代码如下所示:

    3866     rBackslash = /\\/g,

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    4399         TAG: function( match, curLoop ) {

    4400             return match[1].replace( rBackslash, "" ).toLowerCase();

    4401         },

    4475     },

    4749 };

    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节。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    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         },

    4475     },

    4749 };

    第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节。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    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         },

    4475     },

    4749 };

    第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节。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    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         },

    4475     },

    4749 };

    第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节。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4371     preFilter: {

    4470         POS: function( match ) {

    4471             match.unshift( true );

    4472 

    4473             return match;

    4474         }

    4475     },

    4749 };

    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 个分组:伪类、引号、伪类参数

    相关代码如下所示,为了方便解释,代码中增加了示例和注释:

    4221 var Expr = Sizzle.selectors = {

    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 = elem.nodeName.toLowerCase();

    4550             return (name === "input" || name === "button") && "reset" === elem.type;

    4551         },

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

    4553         button: function( elem ) {

    4554             var name = elem.nodeName.toLowerCase();

    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     },

    4749 };

    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 个分组:位置伪类、位置伪类参数

    相关代码如下所示,为了方便解释,代码中增加了示例和注释:

    4221 var Expr = Sizzle.selectors = {

    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)') 匹配一个指定下标的元素,从 0 开始计数

    4595          eq: function( elem, i, match ) {

    4596              return match[3] - 0 === i;

    4597          }

    4598      },

    4749 };

    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的总体源码结构如下所示:

    var Expr = Sizzle.selectors = {

       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节。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    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     }

    4749 };

    第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 如果某个元素是父元素的唯一子元素,则匹配;如果父元素还含有多个子元素,则不匹配

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    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 === 1 ) { 

    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         },

    4748     }

    4749 };

    第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节。

    3.?ID

    ID过滤函数Sizzle.selectors.filter.ID( elem, match )用于检查元素的属性id是否与指定的id相等。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    4691         ID: function( elem, match ) {

    4692             return elem.nodeType === 1 && elem.getAttribute("id") === match;

    4693         },

    4748     }

    4749 };

    4.?TAG

    标签过滤函数Sizzle.selectors.filter.TAG( elem, match )用于检查元素的标签名nodeName是否与指定的标签名相等。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    4691         ID: function( elem, match ) {

    4692             return elem.nodeType === 1 && elem.getAttribute("id") === match;

    4693         },

    4748     }

    4749 };

    5.?CLASS

    类样式过滤函数Sizzle.selectors.filter.CLASS( elem, match )用于检查元素的类样式className是否含有指定的类样式。检查技巧是在类样式前后加空格,然后判断字符串方法indexOf()的返回值。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    4699         CLASS: function( elem, match ) {

    4700             return (" " + (elem.className || elem.getAttribute("class")) + " ")

    4701                 .indexOf( match ) > -1;

    4702         },

    4748     }

    4749 };

    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"] 匹配含有指定属性,并且当前属性值含有指定单词的元素。单词之间用空格分隔

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    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         },

    4748     }

    4749 };

    第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;

    7.?POS

    位置伪类过滤函数Sizzle.selectors.filter.POS( elem, match, i, array )用于检查元素是否匹配位置伪类,该函数通过调用位置伪类过滤函数集Sizzle.selectors.setFilters中对应的位置伪类过滤函数来实现,具体请参见3.9.6节。调用关系如图3-1所示。

    相关代码如下所示:

    4221 var Expr = Sizzle.selectors = {

    4599     filter: {

    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    }

    4749 };

    3.10 工具方法

    3.10.1 Sizzle.uniqueSort( results )

    工具方法Sizzle.uniqueSort( results )负责对元素集合中的元素按照出现在文档中的顺序进行排序,并删除重复元素。

    相关代码如下所示:

    4026 Sizzle.uniqueSort = function( results ) {

    4027     if ( sortOrder ) {

    4028         hasDuplicate = baseHasDuplicate;

    4029         results.sort( sortOrder );

    4030 

    4031         if ( hasDuplicate ) {

    4032             for ( var i = 1; i < results.length; i++ ) {

    4033                 if ( results[i] === results[ i - 1 ] ) {

    4034                     results.splice( i--, 1 );

    4035                 }

    4036             }

    4037         }

    4038     }

    4039 

    4040     return results;

    4041 };

    第4029行:调用数组方法sort()对数组中的元素进行排序。其中,sortOrder( a, b )是比较函数,负责比较元素a和元素b在文档中的位置。如果比较函数sortOrder( a, b )遇到相等的元素,即重复元素,会设置变量hasDuplicate为true。关于比较函数sortOrder( a, b )的具体说明请参见3.10.2节。

    第4031~4037行:如果变量hasDuplicate为true,表示存在重复元素,则遍历数组results,比较相邻元素是否相等,如果相等则删除。

    第4028行:开始排序和去重时,先设置变量hasDuplicate的默认值为变量baseHas

    Duplicate,变量baseHasDuplicate指示了JavaScript引擎在排序时是否会进行优化。

    相关代码如下所示:

    3864     hasDuplicate = false,

    3865     baseHasDuplicate = true,

    3870 // Here we check if the JavaScript engine is using some sort of

    3871 // optimization where it does not always call our comparision

    3872 // function. If that is the case, discard the hasDuplicate value.

    3873 // Thus far that includes Google Chrome.

    3874 [0, 0].sort(function() {

    3875     baseHasDuplicate = false;

    3876     return 0;

    3877 });

    第3874~3877行:检查JavaScript引擎在排序时是否会进行优化。在早期的Chrome浏览器中,排序时如果遇到相等的元素,不会调用比较函数,新版本中已经取消了这一优化。如果遇到相等元素便不调用比较函数,此时变量baseHasDuplicate默认为true,即只能假设数组中含有重复元素;如果遇到相等元素时仍然会调用比较函数,则变量baseHasDuplicate将被设置为false,这种情况下需要在比较函数中判断是否含有重复元素。

    读者可以访问http://bugs.jquery.com/ticket/5380查看该bug的描述。

    3.10.2 sortOrder( a, b )

    函数sortOrder( a, b )负责比较元素a和元素b在文档中的位置。如果元素a在元素b之前,则返回-1;如果元素a在元素b之后,则返回1;如果元素a与元素b相等,则返回0。

    函数sortOrder( a, b )通过调用原生方法compareDocumentPosition()或比较原生属性source

    Index来实现。原生方法compareDocumentPosition()用于比较两个元素的文档位置;原生属性sourceIndex则返回元素在文档中的序号,返回值等于该元素在document.getElementsBy

    TagName('*')返回的数组中的下标。更多信息请访问http://www.quirksmode.org/dom/w3c_core.html。

    函数sortOrder( a, b )执行的3个关键步骤如下:

    1)如果浏览器支持原生方法compareDocumentPosition(),则调用该方法比较元素位置。

    2)如果浏览器支持原生属性sourceIndex,则用该属性比较元素位置。

    3)否则比较祖先元素的文档位置。

    下面来看看该函数的源码实现。

    1.?浏览器支持原生方法compareDocumentPosition()的情况

    如果浏览器支持原生方法compareDocumentPosition(),则调用该方法比较元素位置。相关代码如下所示:

    4805 var sortOrder, siblingCheck;

    4806 

    4807 if ( document.documentElement.compareDocumentPosition ) {

    4808     sortOrder = function( a, b ) {

    4809        if ( a === b ) {

    4810            hasDuplicate = true;

    4811            return 0;

    4812        }

    4813 

    4814        if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {

    4815           return a.compareDocumentPosition ? -1 : 1;

    4816        }

    4817 

    4818        return a.compareDocumentPosition(b) & 4 ? -1 : 1;

    4819     };

    4820 

    2.?浏览器支持原生属性sourceIndex的情况

    如果浏览器支持原生属性sourceIndex,则用该属性比较元素位置。

    相关代码如下所示:

    4821 } else {

    4822     sortOrder = function( a, b ) {

    4823         // The nodes are identical, we can exit early

    4824         if ( a === b ) {

    4825            hasDuplicate = true;

    4826            return 0;

    4827 

    4828         // Fallback to using sourceIndex (in IE) if it's available on both nodes

    4829         } else if ( a.sourceIndex && b.sourceIndex ) {

    4830             return a.sourceIndex - b.sourceIndex;

    4831         }

    4832 

    3.?否则比较祖先元素的文档位置

    (1)元素a和元素b是兄弟元素的情况

    相关代码如下所示:

    4833         var al, bl,

    4834             ap = [],

    4835             bp = [],

    4836             aup = a.parentNode,

    4837             bup = b.parentNode,

    4838             cur = aup;

    4839 

    4840         // If the nodes are siblings (or identical) we can do a quick check

    4841         if ( aup === bup ) {

    4842             return siblingCheck( a, b );

    4843 

    第4836~4837行、第4841~4842行:变量aup是元素a的父元素,变量bup是元素b的父元素。如果变量aup与变量bup相等,说明元素a和元素b是兄弟元素,则调用函数siblingCheck()比较元素a和元素b的文档位置。函数siblingCheck( a, b, ret )负责比较兄弟元素的文档位置,稍后会看到该函数的源码实现和分析。

    (2)没有找到父元素的情况

    相关代码如下所示:

    4844         // If no parents were found then the nodes are disconnected

    4845         } else if ( !aup ) {

    4846             return -1;

    4847 

    4848         } else if ( !bup ) {

    4849             return 1;

    4850         }

    4851 

    第4845~4850行:如果元素a没有父元素,则认为元素a不在文档中,返回-1,元素a将排在元素b之前;如果元素b没有父元素,则认为元素b不在文档中,返回1,元素b将排在元素a之前。

    (3)查找元素a和元素b的祖先元素

    相关代码如下所示:

    4852         // Otherwise they're somewhere else in the tree so we need

    4853         // to build up a full list of the parentNodes for comparison

    4854         while ( cur ) {

    4855             ap.unshift( cur );

    4856             cur = cur.parentNode;

    4857         }

    4858 

    4859         cur = bup;

    4860 

    4861         while ( cur ) {

    4862             bp.unshift( cur );

    4863             cur = cur.parentNode;

    4864         }

    4865 

    (4)比较祖先元素的文档位置

    相关代码如下所示:

    4866         al = ap.length;

    4867         bl = bp.length;

    4868 

    4869         // Start walking down the tree looking for a discrepancy

    4870         for ( var i = 0; i < al && i < bl; i++ ) {

    4871             if ( ap[i] !== bp[i] ) {

    4872                 return siblingCheck( ap[i], bp[i] );

    4873             }

    4874         }

    4875 

    第4866~4874行:从最顶层的祖先元素开始向下遍历,如果祖先元素ap[i]与bp[i]不是同一个元素,那么它们必然是兄弟元素,此时可以通过比较两个祖先元素的文档位置,来确定元素a和元素b的相对位置。

    (5)元素a和元素b的文档深度不一致的情况

    相关代码如下所示:

    4876         // We ended someplace up the tree so do a sibling check

    4877         return i === al ?

    4878             siblingCheck( a, bp[i], -1 ) :

    4879             siblingCheck( ap[i], b, 1 );

    4880     };

    4881

    第4877~4878行:如果元素a的文档深度较小,此时元素a与元素b的祖先元素bp[i]要么是兄弟元素,要么是同一个元素,可以调用函数siblingCheck( a, b, ret )比较文档位置。

    第4877~4879行:如果元素b的文档深度较小,此时元素a的祖先元素ap[i]与元素b要么是兄弟元素,要么是同一个元素,可以调用函数siblingCheck( a, b, ret )比较文档位置。

    (6)siblingCheck( a, b, ret )

    函数siblingCheck( a, b, ret )负责比较兄弟元素的文档位置。该函数从元素a向后遍历(nextSibling),如果遇到元素b,说明元素a在元素b之前,则返回-1;如果一直没遇到,说明元素a在元素b之后,则返回1。

    相关代码如下所示:

    4882     siblingCheck = function( a, b, ret ) {

    4883         if ( a === b ) {

    4884             return ret;

    4885         }

    4886 

    4887         var cur = a.nextSibling;

    4888 

    4889         while ( cur ) {

    4890             if ( cur === b ) {

    4891                 return -1;

    4892             }

    4893 

    4894             cur = cur.nextSibling;

    4895         }

    4896 

    4897         return 1;

    4898     };

    4899 }

    3.10.3 Sizzle.contains( a, b )

    工具方法Sizzle.contains( a, b )负责检测元素a是否包含元素b。该方法通过调用原生方法contains()或compareDocumentPosition()实现。原生方法contains()用于检测一个元素是否包含另一个元素;原生方法compareDocumentPosition()用于比较两个元素的文档位置,更多信息请访问以下网址:

    http://ejohn.org/blog/comparing-document-position/

    http://www.quirksmode.org/dom/w3c_core.html#miscellaneous

    相关代码如下所示:

    5242 if ( document.documentElement.contains ) {

    5243     Sizzle.contains = function( a, b ) {

    5244        return a !== b && (a.contains ? a.contains(b) : true);

    5245     };

    5246 

    5247 } else if ( document.documentElement.compareDocumentPosition ) {

    5248     Sizzle.contains = function( a, b ) {

    5249        return !!(a.compareDocumentPosition(b) & 16);

    5250     };

    5251 

    5252 } else {

    5253     Sizzle.contains = function() {

    5254        return false;

    5255     };

    5256 }

    3.10.4 Sizzle.error( msg )

    工具方法Sizzle.error( msg )用于抛出一个含有选择器表达式语法错误信息的异常。

    相关代码如下所示:

    4178 Sizzle.error = function( msg ) {

    4179     throw new Error( "Syntax error, unrecognized expression: " + msg );

    4180 };

    3.10.5 Sizzle.getText( elem )

    工具方法Sizzle.getText( elem )用于获取元素集合中所有元素合并后的文本内容。

    相关代码如下所示:

    4182 /**

    4183 * Utility function for retreiving the text value of an array of DOM nodes

    4184 * @param {Array|Element} elem

    4185 */

    4186 var getText = Sizzle.getText = function( elem ) {

    4187     var i, node,

    4188         nodeType = elem.nodeType,

    4189         ret = "";

    4190 

    4191     if ( nodeType ) {

    4192         if ( nodeType === 1 || nodeType === 9 ) {

    4193             // Use textContent || innerText for elements

    4194             if ( typeof elem.textContent === 'string' ) {

    4195                 return elem.textContent;

    4196             } else if ( typeof elem.innerText === 'string' ) {

    4197                 // Replace IE's carriage returns

    4198                 return elem.innerText.replace( rReturn, '' );

    4199             } else {

    4200                 // Traverse it's children

    4201                 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {

    4202                     ret += getText( elem );

    4203                 }

    4204             }

    4205         } else if ( nodeType === 3 || nodeType === 4 ) {

    4206             return elem.nodeValue;

    4207         }

    4208     } else {

    4209 

    4210         // If no nodeType, this is expected to be an array

    4211         for ( i = 0; (node = elem[i]); i++ ) {

    4212             // Do not traverse comment nodes

    4213             if ( node.nodeType !== 8 ) {

    4214                 ret += getText( node );

    4215             }

    4216         }

    4217     }

    4218     return ret;

    4219 };

    第4191~4207行:如果参数elem是元素,则尝试读取属性textContent或innerText,如果不支持则遍历子元素,递归调用工具函数getText( elem )来获取每个子元素的文本内容,并合并;如果参数elem是Text节点或CDATASection节点,则直接返回节点值nodeValue。

    第4208~4217行:否则认为参数elem是元素集合,遍历该元素集合,递归调用函数getText(elem)获取每个元素的文本内容,并合并。

    第4218行:最后返回合并后的文本内容。

    3.11 便捷方法

    3.11.1 Sizzle.matches( expr, set )

    便捷方法Sizzle.matches( expr, set )使用指定的选择器表达式expr对元素集合set进行过滤,并返回过滤结果。

    该方法通过简单地调用函数Sizzle( selector, context, results, seed )来实现,调用时会将元素集合set作为参数seed传入。

    相关代码如下所示:

    4043 Sizzle.matches = function( expr, set ) {

    4044     return Sizzle( expr, null, null, set );

    4045 };

    3.11.2 Sizzle.matchesSelector( node, expr )

    便捷方法Sizzle.matchesSelector( node, expr )用于检查某个元素node是否匹配选择器表达式expr。

    如果浏览器支持原生方法matchesSelector()、mozMatchesSelector()、webkitMatchesSelector()、msMatchesSelector()中的一种,则尝试调用原生方法检查元素与选择器表达式是否匹配;如果浏览器不支持原生方法,或者支持但是检查失败(抛出异常),则调用函数Sizzle( selector, context, results, seed ),检查其返回值的长度是否大于0,调用时会将元素node封装成数组作为参数seed传入。

    相关代码如下所示:

    4047 Sizzle.matchesSelector = function( node, expr ) {

    4048     return Sizzle( expr, null, null, [node] ).length > 0;

    4049 };

    5095 (function(){

    5096     var html = document.documentElement,

    5097         matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;

    5098 

    5099     if ( matches ) {

    5100         // Check to see if it's possible to do matchesSelector

    5101         // on a disconnected node (IE 9 fails this)

    5102         var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),

    5103             pseudoWorks = false;

    5104 

    5105         try {

    5106             // This should fail with an exception

    5107             // Gecko does not error, returns false instead

    5108             matches.call( document.documentElement, "[test!='']:sizzle" );

    5109     

    5110         } catch( pseudoError ) {

    5111             pseudoWorks = true;

    5112         }

    5113 

    5114         Sizzle.matchesSelector = function( node, expr ) {

    5115             // Make sure that attribute selectors are quoted

    5116             expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

    5117 

    5118             if ( !Sizzle.isXML( node ) ) {

    5119                 try { 

    5120                     if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {

    5121                         var ret = matches.call( node, expr );

    5122 

    5123                         // IE 9's matchesSelector returns false on disconnected nodes

    5124                         if ( ret || !disconnectedMatch ||

    5125                                 // As well, disconnected nodes are said to be in a document

    5126                                 // fragment in IE 9, so check for that

    5127                                 node.document && node.document.nodeType !== 11 ) {

    5128                             return ret;

    5129                         }

    5130                     }

    5131                 } catch(e) {}

    5132             }

    5133 

    5134             return Sizzle(expr, null, null, [node]).length > 0;

    5135         };

    5136     }

    5137 })();

    3.12 jQuery扩展

    3.12.1 暴露Sizzle给jQuery

    下面的代码将Sizzle的方法和属性暴露给了jQuery:

    5288 // EXPOSE

    5289 // Override sizzle attribute retrieval

    5290 Sizzle.attr = jQuery.attr;

    5291 Sizzle.selectors.attrMap = {};

    5292 jQuery.find = Sizzle;

    5293 jQuery.expr = Sizzle.selectors;

    5294 jQuery.expr[":"] = jQuery.expr.filters;

    5295 jQuery.unique = Sizzle.uniqueSort;

    5296 jQuery.text = Sizzle.getText;

    5297 jQuery.isXMLDoc = Sizzle.isXML;

    5298 jQuery.contains = Sizzle.contains;

    3.12.2 .find( selector )

    方法.find( selector )用于获取匹配元素集合中的每个元素的后代元素,可以用一个选择器表达式、jQuery对象或DOM元素来过滤查找结果,并用匹配的元素构造一个新的jQuery对象作为返回值。该方法通过调用函数 Sizzle( selector, context, results, seed )来实现,并在后者的基础上增加了对多上下文和链式语法的支持。

    方法.find( selector )执行的2个关键步骤如下:

    1)如果参数select是jQuery对象或DOM元素,则检查其是否是当前元素集合中某个元素的后代元素,是则保留,不是则丢弃。

    2)如果参数selector是字符串,则遍历当前元素集合,以每个元素为上下文,调用方法jQuery.find( selector, context, results, seed )也就是Sizzle( selector, context, results, seed ),查找匹配的后代元素,并将查找结果合并、去重。

    相关代码如下所示:

    5319 jQuery.fn.extend({

    5320     find: function( selector ) {

    5321         var self = this,

    5322             i, l;

    5323 

    5324         if ( typeof selector !== "string" ) {

    5325             return jQuery( selector ).filter(function() {

    5326                 for ( i = 0, l = self.length; i < l; i++ ) {

    5327                     if ( jQuery.contains( self[ i ], this ) ) {

    5328                         return true;

    5329                     }

    5330                 }

    5331             });

    5332         }

    5333 

    5334         var ret = this.pushStack( "", "find", selector ),

    5335             length, n, r;

    5336 

    5337         for ( i = 0, l = this.length; i < l; i++ ) {

    5338             length = ret.length;

    5339             jQuery.find( selector, this[i], ret );

    5340 

    5341             if ( i > 0 ) {

    5342                 // Make sure that the results are unique

    5343                 for ( n = length; n < ret.length; n++ ) {

    5344                     for ( r = 0; r < length; r++ ) {

    5345                         if ( ret[r] === ret[n] ) {

    5346                             ret.splice(n--, 1);

    5347                             break;

    5348                         }

    5349                     }

    5350                 }

    5351             }

    5352         }

    5353 

    5354         return ret;

    5355     },

    5470 });

    第5320行:定义方法.find( selector ),参数selector可以是选择器表达式,也可以是jQuery对象或DOM元素。

    第5324~5332行:如果参数selector不是字符串,则认为该参数是jQuery对象或DOM元素,此时先将该参数统一封装为一个新jQuery对象,然后遍历新jQuery对象,检查其中的元素是否是当前jQuery对象中某个元素的后代元素,如果是则保留,不是则丢弃。最后返回含有新jQuery对象子集的另一个新jQuery对象。

    第5334行:调用方法.pushStack( elements, name, arguments )构造一个新的空jQuery对象,并将其作为返回值,后面找到的元素都将被添加到该jQuery对象中。

    第5337~5352行:如果参数selector是字符串,则遍历当前元素集合,以每个元素为上下文,调用方法jQuery.find( selector, context, results, seed )也就是Sizzle( selector, context, results, seed ),查找匹配的后代元素,并将查找结果合并、去重。

    第5341~5351行:从当前元素集合的第2个元素开始,遍历新找到的元素数组,移除其中与已找到的元素相等的元素,以确保找到的元素是唯一的。变量length表示已找到的元素集合的长度,也就是新找到的元素被插入的开始位置;移除时通过将循环变量n自减1,确保接下来的遍历不会漏掉元素。

    3.12.3 .has( target )

    方法.has( target )用当前jQuery对象的子集构造一个新jQuery对象,其中只保留子元素可以匹配参数target的元素。参数target可以是选择器表达式、jQuery对象或DOM元素。该方法通过调用方法.filter( selector )遍历当前匹配元素集合,通过调用jQuery.contains( a, b ),也就是Sizzle.contains( a, b ),检查匹配元素是否包含了可以匹配参数target的子元素。

    相关代码如下所示:

    5319 jQuery.fn.extend({

    5357     has: function( target ) {

    5358        var targets = jQuery( target );

    5359        return this.filter(function() {

    5360           for ( var i = 0, l = targets.length; i < l; i++ ) {

    5361               if ( jQuery.contains( this, targets[i] ) ) {

    5362                  return true;

    5363               }

    5364           }

    5365        });

    5366     },

    5470 });

    第5357行:定义方法.has( target ),参数target可以是选择器表达式、jQuery对象及元素。

    第5358行:构造匹配参数target的jQuery对象。

    第5359~5365行:调用方法.filter( selector )遍历当前匹配元素集合,检查每个匹配元素是否包含了参数target所匹配的某个元素,如果包含则保留,不包含则丢弃。

    3.12.4 .not( selector )、.filter( selector )

    方法.not( selector )用当前jQuery对象的子集构造一个新jQuery对象,其中只保留与参数selector不匹配的元素。参数selector可以是选择器表达式、jQuery对象、函数、DOM元素或DOM元素数组。

    方法.filter( selector )用当前jQuery对象的子集构造一个新jQuery对象,其中只保留与参数selector匹配的元素。参数selector可以是选择器表达式、jQuery对象、函数、DOM元素或DOM元素数组。

    方法.not( selector )和.filter( selector )通过调用函数winnow( elements, qualifier, keep )对当前匹配元素集合进行过滤,并用其返回值构造一个新jQuery对象。不过,这两个方法的过滤行为正好相反,这种差异通过调用函数winnow( elements, qualifier, keep )时传入不同的参数keep来实现。

    相关代码如下所示:

    5319 jQuery.fn.extend({

    5368     not: function( selector ) {

    5369         return this.pushStack( winnow(this, selector, false), "not", 

    selector );

    5370     },

    5371 

    5372     filter: function( selector ) {

    5373         return this.pushStack( winnow(this, selector, true), "filter", 

    selector );

    5374     },

    5470 });

    5590 // Implement the identical functionality for filter and not

    5591 function winnow( elements, qualifier, keep ) {

    5592 

    5593     // Can't pass null or undefined to indexOf in Firefox 4

    5594     // Set to 0 to skip string check

    5595     qualifier = qualifier || 0;

    5596 

    5597     if ( jQuery.isFunction( qualifier ) ) {

    5598         return jQuery.grep(elements, function( elem, i ) {

    5599             var retVal = !!qualifier.call( elem, i, elem );

    5600             return retVal === keep;

    5601         });

    5602 

    5603     } else if ( qualifier.nodeType ) {

    5604         return jQuery.grep(elements, function( elem, i ) {

    5605             return ( elem === qualifier ) === keep;

    5606         });

    5607 

    5608     } else if ( typeof qualifier === "string" ) {

    5609         var filtered = jQuery.grep(elements, function( elem ) {

    5610             return elem.nodeType === 1;

    5611         });

    5612 

    5613         if ( isSimple.test( qualifier ) ) {

    5614             return jQuery.filter(qualifier, filtered, !keep);

    5615         } else {

    5616             qualifier = jQuery.filter( qualifier, filtered );

    5617         }

    5618     }

    5619 

    5620     return jQuery.grep(elements, function( elem, i ) {

    5621         return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;

    5622     });

    5623 }

    第5591行:函数winnow( elements, qualifier, keep )负责过滤元素集合,它接受3个参数:

    参数elements:待过滤的元素集合。

    参数qualifier:用于过滤元素集合elements,可选值有函数、DOM元素、选择器表达式、DOM元素数组、jQuery对象。

    参数keep:布尔值。如果为true,则保留匹配元素,如果为false,则保留不匹配元素。

    第5597~5601行:如果参数qualifier是函数,则调用方法jQuery.grep( array, function

    ( elementOfArray, indexInArray )[, invert] )遍历元素集合elements,在每个元素上执行该函数,然后将返回值与参数keep进行比较,如果一致则保留,不一致则丢弃。

    第5603~5606行:如果参数qualifier是DOM元素,则调用方法jQuery.grep( array, function( elementOfArray, indexInArray )[, invert] )遍历元素集合elements,检查其中的每个元素是否与参数qualifier相等,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。

    第5608~5622行:如果参数qualifier是字符串,则先过滤出与该参数匹配的元素集合,然后调用方法jQuery.grep( array, function( elementOfArray, indexInArray )[, invert] )遍历元素集合elements,检查其中的每个元素是否在过滤结果中,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。

    第5620~5622行:如果参数qualifier是DOM元素数组或jQuery对象,则调用方法jQuery.grep( array, function(elementOfArray, indexInArray)[, invert] )遍历元素集合 elements,检查其中的每个元素是否在参数qualifier中,然后将检查结果与参数keep进行比较,如果一致则保留,不一致则丢弃。

    关于方法jQuery.grep( array, function(elementOfArray, indexInArray)[, invert] )的介绍和分析请参见2.8.8节。

    3.12.5 .is( selector )

    方法.is( selector )用选择器表达式、DOM元素、jQuery对象或函数来检查当前匹配元素集合,只要其中某个元素可以匹配给定的参数就返回true。

    相关代码如下所示:

    5293 jQuery.expr = Sizzle.selectors;

    5310     POS = jQuery.expr.match.POS,

    5319 jQuery.fn.extend({

    5376     is: function( selector ) {

    5377         return !!selector && ( 

    5378             typeof selector === "string" ?

    5379                 // If this is a positional selector, check membership in the returned set

    5380                 // so $("p:first").is("p:last") won't return true for a doc with two "p".

    5381                 POS.test( selector ) ? 

    5382                     jQuery( selector, this.context ).index( this[0] ) >= 0 :

    5383                     jQuery.filter( selector, this ).length > 0 :

    5384                 this.filter( selector ).length > 0 );

    5385     },

    5470 });

    第5376行:定义方法.is( selector ),其中参数selector可以是选择器表达式、DOM元素、jQuery对象或函数。

    第5378行、第5381行、第5382行:如果参数selector是字符串,并且含有位置伪类,则先调用构造函数jQuery( selector[, context] )查找参数selector匹配的元素集合,然后检查当前匹配元素集合中的第一个元素是否在查找结果中,如果在结果中则返回true,如果不在则返回false。

    第5378行、第5381行、第5383行:如果参数selector是字符串,并且不含有位置伪类,则调用方法jQuery.filter( expr, elems, not )用参数selector过滤当前匹配元素集合,然后检查过滤结果中是否仍有元素,如果有则返回true,如果没有则返回false。

    第5378行、第5384行:如果参数selector不是字符串,则可能是DOM元素、jQuery对象或函数,先调用方法.filter( selector )用参数selector过滤当前匹配元素集合,然后检查过滤结果中是否仍有元素,如果有则返回true,如果没有则返回false。

    3.12.6 .closest( selectors, context )

    方法.closest( selectors [, context] )用于在当前匹配元素集合和它们的祖先元素中查找与参数selectors匹配的最近元素,并用查找结果构造一个新jQuery对象。

    方法.closest( selectors [, context] )与.parents( [selector] )的行为相似,都是沿着DOM树向上查找匹配元素,需要注意的是两者的差别,具体如表3-16所示。

    表3-16  .closest( selectors [, context] )、.parents( [selector] )

    序  号 .closest( selectors [, context] ) .parents( [selector] )

    1 从每个匹配元素开始 从每个匹配元素的父元素开始

    2 沿着DOM树向上遍历,直到找到匹配参数selectors的元素 沿着DOM树向上遍历,查找所有与参数selectors匹配的元素

    相关代码如下所示:

    5293 jQuery.expr = Sizzle.selectors;

    5310     POS = jQuery.expr.match.POS,

    5319 jQuery.fn.extend({

    5387     closest: function( selectors, context ) {

    5388         var ret = [], i, l, cur = this[0];

    5389         

    5390         // Array (deprecated as of jQuery 1.7)

    5391         if ( jQuery.isArray( selectors ) ) {

    5392             var level = 1;

    5393 

    5394             while ( cur && cur.ownerDocument && cur !== context ) {

    5395                 for ( i = 0; i < selectors.length; i++ ) {

    5396 

    5397                     if ( jQuery( cur ).is( selectors[ i ] ) ) {

    5398                         ret.push({ selector: selectors[ i ], elem: cur, level: level });

    5399                     }

    5400                 }

    5401 

    5402                 cur = cur.parentNode;

    5403                 level++;

    5404             }

    5405 

    5406             return ret;

    5407         }

    5408 

    5409         // String

    5410         var pos = POS.test( selectors ) || typeof selectors !== "string" ?

    5411                 jQuery( selectors, context || this.context ) :

    5412                 0;

    5413 

    5414         for ( i = 0, l = this.length; i < l; i++ ) {

    5415             cur = this[i];

    5416 

    5417             while ( cur ) {

    5418                 if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector (cur, selectors) ) {

    5419                     ret.push( cur );

    5420                     break;

    5421 

    5422                 } else {

    5423                     cur = cur.parentNode;

    5424                     if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {

    5425                         break;

    5426                     }

    5427                 }

    5428             }

    5429         }

    5430 

    5431         ret = ret.length > 1 ? jQuery.unique( ret ) : ret;

    5432 

    5433         return this.pushStack( ret, "closest", selectors );

    5434     },

    5470 });

    第5387行:定义方法.closest( selectors [, context] ),它接受2个参数:

    参数selectors:用于匹配DOM元素,可选值有:选择器表达式、jQuery对象、DOM元素、数组。

    参数context:可选的上下文,用于限定查找范围。

    第5391~5407行:如果参数selectors是数组,则从当前匹配元素集合中的第一个元素开始沿着DOM树向上遍历,查找与数组selector中的元素匹配的元素,直到遇到上下文context或document对象为止。如果找到与数组selector中的元素匹配的元素,则放入数组ret中,其格式为:

    { selector: 参数 selector 中的元素, elem: 匹配元素, level: 向上查找的层级 }

    第5414~5429行:遍历当前元素集合,在每个匹配元素和它的祖先元素中查找与参数selectors匹配的最近元素。

    第5417~5428行:在向上遍历的过程中,如果找到了与参数selectors匹配的元素,则把它插入数组ret,然后跳出while循环,继续在下一个匹配元素和它的祖先元素中查找。

    第5422~5427行:如果当前元素cur不匹配参数selectors,则读取它的父元素,继续向上遍历。如果父元素不存在,或者父元素不在文档中,或者父元素是上下文,或者父元素是文档片段,则跳出while循环,继续在下一个匹配元素和它的祖先元素中查找。

    第5431行:如果找到了多个匹配参数selectors的元素,则调用方法jQuery.unique( results ),也就是Sizzle.uniqueSort( results ),执行排序和去重操作。

    第5433行:最后用找到的元素构造一个新jQuery对象,并返回。

    3.12.7 .index( elem )

    方法.index( elem )用于判断元素在元素集合中的下标位置。该方法的行为随参数elem的不同而不同,如表3-17所示。

    表3-17 .index( elem )

    序  号 参数 elem 行  为

    1 没有传入 返回第一个匹配元素相对于其兄弟元素的下标位置

    2 选择器表达式 返回第一个匹配元素在选择器表达式所匹配的元素集合中的位置

    3 DOM元素 返回DOM元素在当前匹配元素集合中的位置

    4 jQuery对象 返回jQuery对象的第一个元素在当前匹配元素集合中的位置

    相关代码如下所示:

    5319 jQuery.fn.extend({

    5436     // Determine the position of an element within

    5437     // the matched set of elements

    5438     index: function( elem ) {

    5439 

    5440         // No argument, return index in parent

    5441         if ( !elem ) {

    5442             return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;

    5443         }

    5444 

    5445         // index in selector

    5446         if ( typeof elem === "string" ) {

    5447             return jQuery.inArray( this[0], jQuery( elem ) );

    5448         }

    5449 

    5450         // Locate the position of the desired element

    5451         return jQuery.inArray(

    5452             // If it receives a jQuery object, the first element is used

    5453             elem.jquery ? elem[0] : elem, this );

    5454     },

    5470 });

    第5441~5443行:如果没有参数,则返回当前匹配元素集合中第一个元素相对于其兄弟元素的位置。如果第一个元素没有父元素,则返回-1。

    第5446~5448行:如果参数elem是字符串,则调用构造函数jQuery( selector[, context ] )查找参数elem匹配的元素集合,然后调用方法jQuery.inArray( elem, array, i )获取当前匹配元素集合中第一个元素在查找结果中的位置,并返回。

    第5451~5453行:如果参数elem是DOM元素,则调用方法jQuery.inArray( elem, array, i )获取参数elem在当前元素匹配集合中的位置,并返回;如果参数elem是jQuery对象,则调用方法jQuery.inArray( elem, array, i )获取参数elem的第一个元素在当前匹配元素集合中的位置,并返回。

    3.12.8 .add( selector, context )

    方法.add( selector, context )用当前jQuery对象中的元素和传入的参数构造一个新jQuery对象。构造函数jQuery()可以接受的参数格式,该方法都可以接受。另外,该方法不会改变当前jQuery对象。

    相关代码如下所示:

    5319 jQuery.fn.extend({

    5456     add: function( selector, context ) {

    5457         var set = typeof selector === "string" ?

    5458                 jQuery( selector, context ) :

    5459                 jQuery.makeArray( selector && selector.nodeType ? 

    [ selector ] : selector ),

    5460             all = jQuery.merge( this.get(), set );

    5461 

    5462         return this.pushStack( isDisconnected( set[0] ) || isDisconnected

    ( all[0] ) ?

    5463             all :

    5464             jQuery.unique( all ) );

    5465     },

    5470 });

    5472 // A painfully simple check to see if an element is disconnected

    5473 // from a document (should be improved, where feasible).

    5474 function isDisconnected( node ) {

    5475     return !node || !node.parentNode || node.parentNode.nodeType === 11;

    5476 }

    第5457~5459行:如果参数selector是字符串,则调用构造函数jQuery( selector[, context] )查找与之匹配的元素集合,并赋值给变量set;否则调用方法jQuery.makeArray( array, results )将参数selector转换为数组。

    第5460行:调用方法jQuery.merge( first, second )将当前jQuery对象中的元素和元素集合set的元素合并,并赋值给变量all。

    第5462~5464行:用合并后的元素集合all构造一个新jQuery对象。如果元素集合set的第一个元素或合并后的元素集合all中第一个元素不在文档中,则可以直接用元素集合all构造新jQuery对象;否则,需要先调用方法jQuery.unique( results ),也就是Sizzle.uniqueSort( results ),对合并后的元素集合all进行排序和去重,然后再构造jQuery对象。

    第5474~5476行:函数isDisconnected( node )用于简单地判断一个元素是否不在文档中。如果元素不存在,或者父元素不存在,或者父元素是文档片段,则返回true。

    3.12.9 jQuery.filter( expr, elems, not )

    方法jQuery.filter( expr, elems, not )使用指定的选择器表达式expr对元素集合elems进行过滤,并返回过滤结果。如果参数not是true,则保留不匹配元素,否则默认保留匹配元素。

    相关代码如下所示:

    5540 jQuery.extend({

    5541     filter: function( expr, elems, not ) {

    5542         if ( not ) {

    5543             expr = ":not(" + expr + ")";

    5544         }

    5545 

    5546         return elems.length === 1 ?

    5547             jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :

    5548             jQuery.find.matches(expr, elems);

    5549     },

    5588 });

    第5541行:定义方法jQuery.filter( expr, elems, not ),它接受3个参数:

    参数expr:选择器表达式。

    参数elems:待过滤的元素集合。

    参数not:布尔值。如果是true,则保留不匹配元素,否则默认保留匹配元素。

    第5542~5544行:修正选择器表达式expr。

    第5546、5547行:如果元素集合elems中只有一个元素,则调用方法jQuery.find.matches

    Selector( node, expr ),也就是Sizzle.matchesSelector( node, expr ),来检查元素是否匹配选择器表达式expr。

    第5546~5548行:如果含有多个元素,则调用方法jQuery.find.matches( expr, set ),也就是Sizzle.matches( expr, set ),来用选择器表达式expr对元素集合elems进行过滤。

    3.12.10 :animated

    jQuery的动画模块扩展了伪类:animated,用于检测DOM元素是否正在执行动画。在对应的伪类过滤函数jQuery.expr.filters.animated中,会遍历全局动画函数数组jQuery.timers,检查每个动画函数的属性elem是否是当前元素;如果某个动画函数的属性elem是当前元素,则表示当前元素正在执行动画。更多信息请参考第14章。

    相关代码如下所示:

    8842 if ( jQuery.expr && jQuery.expr.filters ) {

    8843     jQuery.expr.filters.animated = function( elem ) {

    8844         return jQuery.grep(jQuery.timers, function( fn ) {

    8845             return elem === fn.elem;

    8846         }).length;

    8847     };

    8848 }

    3.12.11 hidden、:visible

    jQuery的样式操作模块扩展了伪类:hidden和:visible,用来判断DOM元素是否占据布局空间。在对应的伪类过滤函数jQuery.expr.filters.hidden/visible中,通过判断DOM元素的可见宽度offsetWidth和可见高度offsetHeight是否为0,来判断该元素是否占据布局空间。

    注意:设置样式visibility为hidden,或设置样式opacity为0后,DOM元素仍然会占据布局空间。

    相关代码如下所示:

    6816 if ( jQuery.expr && jQuery.expr.filters ) {

    6817     jQuery.expr.filters.hidden = function( elem ) {

    6818         var width = elem.offsetWidth,

    6819             height = elem.offsetHeight;

    6820 

    6821         return ( width === 0 && height === 0 ) 

                     || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");

    6822     };

    6823 

    6824     jQuery.expr.filters.visible = function( elem ) {

    6825         return !jQuery.expr.filters.hidden( elem );

    6826     };

    6827 }

    第6817~6822行:如果DOM元素的可见宽度offsetWidth和可见高度offsetHeight是0,则认为该元素不占据布局空间。如果元素的可见高度offsetHeight不可靠,则检查样式display;如果内联样式display是"none",则认为不占据布局空间;如果未设置行内样式display,但是计算样式display是"none",也认为不占据布局空间。

    第6824~6826行:通过对伪类过滤函数jQuery.expr.filters.hidden( elem )的结果取反,来确定元素是否占据布局空间。

    3.13 总结

    在本章中,对选择器引擎Sizzle做了完整的介绍和分析,总体源码结构见代码清单3-1,方法功能和调用关系见图3-1。在本章的最后还介绍和分析了jQuery对Sizzle的整合和扩展。

    选择器表达式由块表达式和块间关系符组成。块表达式分为3种:简单表达式、属性表达式、伪类表达式;块间关系符分为4种:">"父子关系、""祖先后代关系、"+"紧挨着的兄弟元素、"~"之后的所有兄弟元素;块表达式和块间关系符组成了层级表达式。见图3-3。

    Sizzle( selector, context, results, seed )用于查找与选择器表达式selector匹配的元素集合。如果浏览器支持原生方法querySelectorAll(),则调用该方法查找元素,如果不支持,则模拟该方法的行为。执行过程见图3-2。

    正则chunker用于从选择器表达式中提取块表达式和块间关系符,其分解图和测试用例见图3-4。

    Sizzle.find( expr, context, isXML )负责查找与块表达式匹配的元素集合。该方法按照表达式类型数组Sizzle.selectors.order规定的查找顺序(ID、CLASS、NAME、TAG)逐个尝试查找,如果未找到,则查找上下文的所有后代元素(*)。执行过程见图3-5。

    Sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。该方法通过调用过滤函数集Sizzle.selectors.filter中的过滤函数来执行过滤操作。执行过程见图3-6。

    Sizzle.selectors包含了Sizzle在查找和过滤过程中用到的正则、查找函数、过滤函数,见图3-1。

    Sizzle.selectors.order中定义了查找单个块表达式时的查找顺序,依次是ID、CLASS、NAME、TAG。其中,CLASS需要浏览器支持方法getElementsByClassName()。

    Sizzle.selectors.match/leftMatch中存放了表达式类型和正则的映射,正则用于确定块表达式的类型,并解析其中的参数。解析图见图3-7~图3-14,测试用例见表3-3~表3-11。

    Sizzle.selectors.find中定义了ID、CLASS、NAME、TAG所对应的查找函数。其中,CLASS需要浏览器支持方法getElementsByClassName()。查找函数会返回数组或undefined,内部通过调用相应的原生方法来查找元素,见表3-11。

    Sizzle.selectors.relative中存放了块间关系符和对应的块间关系过滤函数。块间关系过滤函数用于检查映射集checkSet中的元素是否匹配块间关系符左侧的块表达式。支持的块间关系符和对应的过滤方式见表3-2。

    Sizzle.selectors.preFilter中定义了CLASS、ID、TAG、CHILD、ATTR、PSEUDO、POS所对应的预过滤函数。预过滤函用于在过滤函数之前修正与过滤操作相关的参数,每种类型的预过滤函数的修正行为见表3-12;预过滤函数有3种返回值,见表3-13。

    Sizzle.selectors.filters中定义了一组伪类和对应的伪类过滤函数。伪类过滤函数负责检查元素是否匹配伪类,返回一个布尔值。

    Sizzle.selectors.setFilters中定义了一组位置伪类和对应的伪类过滤函数。位置伪类过滤函数通过比较下标来确定元素在集合中的位置,返回一个布尔值。

    Sizzle.selectors.filter中定义了PSEUDO、CHILD、ID、TAG、CLASS、ATTR、POS对应的过滤函数。过滤函数负责检查元素是否匹配过滤表达式,返回一个布尔值。

    相关资源:jQuery技术内幕:深入解析jQuery架构设计与实现原理.pdf
    最新回复(0)