作者:yanyige | 发布时间:2017-03-10 14:42

第三章 Sizzle选择器

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

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

3.1 总体结构

(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.2 选择器表达式

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

序号 | 术语 | 说明和示例 —|—|— 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.3 设计思路

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

Sizzle,它是一款从右向左查找的选择器引擎,提供了与前面3个步骤相对应的核心接口:

3.4 Sizzle( selector, context, results, seed )

函数Sizzle( selector, context, results, seed )用于查找与选择器表达式selector匹配的元素集合。该函数是选择器引擎的入口。

函数Sizzle( selector, context, results, seed ) 执行的6个关键步骤如下:

接下来是源码实现:

1. 定义Sizzle( selector, context, results, seed )

3879 var Sizzle = function( selector, context, results, seed ) {

相关代码如下所示:

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

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

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