jquery 源码分析十二 - Sizzle

在Sizzle中只剩下最后一段大的函数了,就是select,也就是在sizzle中联系compile和Sizzle函数的关键。它在Sizzle中主要处理那些并非是简单的选择器,如包含有:eq(1)的。

直接看源码:

function select( selector, context, results, seed ) {
    var i, tokens, token, type, find,
        match = tokenize( selector );

    if ( !seed ) {
        // 如果只有一个group的话,就减少分析,直接用以下的方法
        if ( match.length === 1 ) {

            // 如果第一个选择符是ID的话,就直接取出来当做上下文
            tokens = match[0] = match[0].slice( 0 );
            if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
                    support.getById && context.nodeType === 9 && documentIsHTML &&
                    Expr.relative[ tokens[1].type ] ) {

                // 返回context下的特定ID的元素
                context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
                if ( !context ) {
                    return results;
                }
                selector = selector.slice( tokens.shift().value.length );
            }

            // 如果有:eq(1)或者>出现在开头就从左往右,否则就是从右到左
            i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
            while ( i-- ) {
                token = tokens[i];

                // 如果有' ','>','+','~',出现就直接break
                if ( Expr.relative[ (type = token.type) ] ) {
                    break;
                }
                if ( (find = Expr.find[ type ]) ) {
                    // 通过寻找相邻元素来扩展context
                    if ( (seed = find(
                        token.matches[0].replace( runescape, funescape ),
                        rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
                    )) ) {

                        // 如果seed没有或者tokens里没有东西了,就可以直接返回了
                        tokens.splice( i, 1 );
                        selector = seed.length && toSelector( tokens );
                        if ( !selector ) {
                            push.apply( results, seed );
                            return results;
                        }

                        break;
                    }
                }
            }
        }
    }

    // 编译并执行筛选函数
    // 提供match是为了防止再次执行tokenize(在我们已经对match更改过的情况下)
    compile( selector, match )(
        seed,
        context,
        !documentIsHTML,
        results,
        rsibling.test( selector ) && testContext( context.parentNode ) || context
    );
    return results;
}

可以看到对于简单的情况,select更改了下context,节省后面产生筛选函数后的闭包深度。对于真正的筛选工作,还是有compile编译得到的超级选择函数来做的。

到这里,Sizzle里所有的大块函数基本都已经讲过了,下面就是在一个选择器传入后函数的运行步骤了。

=======================================================================================

首先是Sizzle暴露到外面的方法,几个主要的方法都在Sizzle闭包结束后运行,源码如下:

jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;

可以看到,jQuery.find完全是调用Sizzle来实现的,那么下面是普通的$('...')和$('...').find('...')形式,我们就要先重温下jQuery.fn.init中的代码片段了

                // HANDLE: $(#id)
// rootjQuery = jQuery( document );
} else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; }
            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }

可以看到,对于选择器的处理,基本上除了ID以外,都使用了$(context).find(selector)形式,我们跟踪到find函数中(注意:此时的find函数是指在prototype中定义的find函数,而不是jQuery.find,两者所处的位置是有区别的),直接上find源码:

    find: function( selector ) {
        var i,
            ret = [],
            self = this,
            len = self.length;

        if ( typeof selector !== "string" ) {
            return this.pushStack( jQuery( selector ).filter(function() {
                for ( i = 0; i < len; i++ ) {
                    if ( jQuery.contains( self[ i ], this ) ) {
                        return true;
                    }
                }
            }) );
        }

        for ( i = 0; i < len; i++ ) {
            jQuery.find( selector, self[ i ], ret );
        }

        ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
        ret.selector = this.selector ? this.selector + " " + selector : selector;
        return ret;
    },

在这个函数中,对于每一个context,都会执行一遍jQuery.find,即Sizzle,保证结果的齐全,同时在后面调用jQuery.unique来保证不重复。

接下来就是进入Sizzle主函数了,Sizzle主函数在之前已经分析过了,就不具体讲了,主要的还是简单的选择器的话就直接处理返回,复杂的话就会调用select函数

function Sizzle( selector, context, results, seed ) {
    var match, elem, m, nodeType,
        // QSA vars
        i, groups, old, nid, newContext, newSelector;

    if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
        setDocument( context );
    }

    context = context || document;
    results = results || [];

    if ( !selector || typeof selector !== "string" ) {
        return results;
    }

    if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
        return [];
    }

    if ( documentIsHTML && !seed ) {

        // Shortcuts
        if ( (match = rquickExpr.exec( selector )) ) {
            // Speed-up: Sizzle("#ID")
            if ( (m = match[1]) ) {
                if ( nodeType === 9 ) {
                    elem = context.getElementById( m );
                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document (jQuery #6963)
                    if ( elem && elem.parentNode ) {
                        // Handle the case where IE, Opera, and Webkit return items
                        // by name instead of ID
                        if ( elem.id === m ) {
                            results.push( elem );
                            return results;
                        }
                    } else {
                        return results;
                    }
                } else {
                    // Context is not a document
                    if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
                        contains( context, elem ) && elem.id === m ) {
                        results.push( elem );
                        return results;
                    }
                }

            // Speed-up: Sizzle("TAG")
            } else if ( match[2] ) {
                push.apply( results, context.getElementsByTagName( selector ) );
                return results;

            // Speed-up: Sizzle(".CLASS")
            } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
                push.apply( results, context.getElementsByClassName( m ) );
                return results;
            }
        }

        // QSA path
        if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
            nid = old = expando;
            newContext = context;
            newSelector = nodeType === 9 && selector;

            // qSA works strangely on Element-rooted queries
            // We can work around this by specifying an extra ID on the root
            // and working up from there (Thanks to Andrew Dupont for the technique)
            // IE 8 doesn't work on object elements
            if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
                groups = tokenize( selector );

                if ( (old = context.getAttribute("id")) ) {
                    nid = old.replace( rescape, "\\$&" );
                } else {
                    context.setAttribute( "id", nid );
                }
                nid = "[id='" + nid + "'] ";

                i = groups.length;
                while ( i-- ) {
                    groups[i] = nid + toSelector( groups[i] );
                }
                newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
                newSelector = groups.join(",");
            }

            if ( newSelector ) {
                try {
                    push.apply( results,
                        newContext.querySelectorAll( newSelector )
                    );
                    return results;
                } catch(qsaError) {
                } finally {
                    if ( !old ) {
                        context.removeAttribute("id");
                    }
                }
            }
        }
    }

    // All others
    return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
View Code

select函数就对传入的selector进行简单的处理,再调用compile函数根据传入的selector和group来生成终极匹配函数,并立刻运行,来获取结果。

这些具体的函数运行步骤的话,还是自己做个demo单步跟踪下比较清晰

好了,jQuery中的Sizzle基本就这写了,剩下的一些小函数随便找点时间看下就懂了,都不讲了,后面的博客就会回到大的jQuery中了

 

posted @ 2014-04-20 18:24  胖蝎子  阅读(264)  评论(0编辑  收藏  举报