jquery 源码分析十 --Sizzle

考虑到Sizzle里面函数互相调用的关系,接下来的函数就不会按从上到下的顺序分析,而是采用调用的顺序进行分析。

这篇博客参考了这篇文章里的说明(靠这篇文章才理解的。。。。)

    首先就是要讲到编译函数,JS是单线程语言,但是在执行程序时,一段函数的运行,会导致线程的阻塞,导致整个程序停顿。这时候异步就出现了,异步的原理不多说了。异步使得JS能够更加灵活的运行,克服了其单线程的限制,但是,大量采用异步会导致整个程序看起来乱糟糟的。所以异步,同步编程的安排还是需要根据实际来控制的。

    好像说偏了。。按照上面参考文章的说法,'JavaScript是单线程的,代码也是同步从上向下执行的,执行流程不会随便地暂停,当遇到异步的情况,从而改变了整个执行流程的时候,我们需要对代码进行自动改写,也就是在程序的执行过程中动态生成并执行新的代码,这个过程我想称之为编译函数的一种运用吧.'

    Sizzle通过compile函数实现了编译函数,compile函数的作用是根据传入的selector进行分词,即调用tokenize,根据返回的结果,对其中每个词形成特定的判断函数,然后将函数集中返回(返回的是一个function)

    废话不多说,直接上源码:

compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
    var i,
        setMatchers = [],
        elementMatchers = [],
        cached = compilerCache[ selector + " " ];

    if ( !cached ) {
        // 判断词是否被分解过
        if ( !group ) {
            group = tokenize( selector );
        }
        i = group.length;
        while ( i-- ) {
            // 调用matcherFromTokens,生成特定词的判断函数
            cached = matcherFromTokens( group[i] );
            // 根据函数添加到不同的数组中
            if ( cached[ expando ] ) {
                setMatchers.push( cached );
            } else {
                elementMatchers.push( cached );
            }
        }

        // 整合,缓存
        cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
    }
    return cached;
};

可以看到,函数中最关键的是通过matcherFromTokens来生成特定判定函数,下面的就是这段函数的源码:

function matcherFromTokens( tokens ) {
    var checkContext, matcher, j,
        len = tokens.length,
        leadingRelative = Expr.relative[ tokens[0].type ],
        implicitRelative = leadingRelative || Expr.relative[" "],
        i = leadingRelative ? 1 : 0,

        // 保证可以从最顶端的context访问到这些元素
        matchContext = addCombinator( function( elem ) {
            return elem === checkContext;
        }, implicitRelative, true ),
        matchAnyContext = addCombinator( function( elem ) {
            return indexOf.call( checkContext, elem ) > -1;
        }, implicitRelative, true ),
        matchers = [ function( elem, context, xml ) {
            return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
                (checkContext = context).nodeType ?
                    matchContext( elem, context, xml ) :
                    matchAnyContext( elem, context, xml ) );
        } ];
    //根据传入的tokens循环
    for ( ; i < len; i++ ) {
        if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
            // 若分词为'+','~',' ','>',调用addCombinator生成总的判断函数
            matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
        } else {
            // 若分词为TAG等
            matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

            // 特殊的位置判断符,如:eq(0)
            if ( matcher[ expando ] ) {
                // 找到下一个相对位置的匹配符
                j = ++i;
                for ( ; j < len; j++ ) {
                    if ( Expr.relative[ tokens[j].type ] ) {
                        break;
                    }
                }
                return setMatcher(
                    i > 1 && elementMatcher( matchers ),
                    i > 1 && toSelector(
                        // If the preceding token was a descendant combinator, insert an implicit any-element `*`
                        tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
                    ).replace( rtrim, "$1" ),
                    matcher,
                    i < j && matcherFromTokens( tokens.slice( i, j ) ),
                    //如果位置伪类后面还有选择器需要筛选
                    j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
                    //如果位置伪类后面还有关系选择器还需要筛选 
                    j < len && toSelector( tokens )
                );
            }
            matchers.push( matcher );
        }
    }

    return elementMatcher( matchers );
}

其实,仔细看可以发现,压入matchers中的函数都只是返回true或false的用来检验元素匹配与否的,但是在这里都不是立刻执行的,而是将其压入,合并。

elementMatcher函数通过闭包的方式,将matches一直保存在内存中。

注意到,关系符出现后就会合并前面的函数

matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];

首先调用的就是elementMatcher,将原来matchers中的内容合并:

function elementMatcher( matchers ) {
    return matchers.length > 1 ?
        function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }
            return true;
        } :
        matchers[0];
}

通过返回一个函数,在返回函数中将matchers中的每一项进行执行,都为true才返回true,否则就直接返回false。

然后是将elementMatcher生成的函数和关系选择符用addCombinator生成一个判断函数:

function addCombinator( matcher, combinator, base ) {
    var dir = combinator.dir,
        checkNonElements = base && dir === "parentNode",
        doneName = done++;

    return combinator.first ?
        // 确定是否是紧跟在后面的子代元素或者兄弟元素
        function( elem, context, xml ) {
            while ( (elem = elem[ dir ]) ) {
                if ( elem.nodeType === 1 || checkNonElements ) {
                    return matcher( elem, context, xml );
                }
            }
        } :

        // 对于不是紧跟的节点
        function( elem, context, xml ) {
            var oldCache, outerCache,
                newCache = [ dirruns, doneName ];

            // 不能再xml节点上设置额外信息,所以不能使用cache
            if ( xml ) {
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        if ( matcher( elem, context, xml ) ) {
                            return true;
                        }
                    }
                }
            } else {
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        outerCache = elem[ expando ] || (elem[ expando ] = {});
                        if ( (oldCache = outerCache[ dir ]) &&
                            oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

                            // 有缓存且符合关系的话就直接返回不需调用matcher了
                            return (newCache[ 2 ] = oldCache[ 2 ]);
                        } else {
                            outerCache[ dir ] = newCache;

                            // 有match时就说明成功了,就可以直接返回,否则要继续循环
                            if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
                                return true;
                            }
                        }
                    }
                }
            }
        };
}

这样通过一些列函数的调用,就完成了当前词的匹配器

在没有'+','~',' ','>'这些词素时,如#a.b[value='c']时,就只需要从右到左检测一遍就可以了。

但是,在有了上面那些词素后,就需要对其进行深一度的打包。

这样,在有很多词素的选择器中,就会形成一个嵌套很深的闭包函数

借图一张来说明:

 对于这些组合之后怎么执行,我继续研究下,应该是通过matcherFromGroupMatchers函数。现在还在学习中,在下一章会详细讲一下。

原文地址:https://www.cnblogs.com/cyITtech/p/3657630.html