美文网首页jQuery源码笔记.jpg
jQuery源码#4 Sizzle 预编译

jQuery源码#4 Sizzle 预编译

作者: 柠檬果然酸 | 来源:发表于2020-04-15 22:38 被阅读0次

    本节内容
    1.Sizzle.compile
    2.matcherFromTokens
    3.elementMatcher
    4.addCombinator

    先来一个例子

    html代码

    <!DOCTYPE>
    <html>
    <head>
        <title></title>
    </head>
    <body>
        <div id="text">
            <p>
                <input type="text" />
            </p>
            <div class="aaron">
                <input type="checkbox" name="readme" value="Submit" />
                <p>Sizzle</p>
            </div>
        </div>
    </body>
    </html>
    

    +

    原始选择器div > p + div.aaron input[type="checkbox"]

    =

    seed集合[input, input]
    重组的选择器div > p + div.aaron [type="checkbox"]
    Token序列

    "="后面的是经过Sizzle.tokenizeSizzle.select的处理获取到的数据

    Expr.filter
    Token序列中每一种type都有对应的处理方法

    Expr.filter = {
        ATTR   : function (name, operator, check) {
        CHILD  : function (type, what, argument, first, last) {
        CLASS  : function (className) {
        ID     : function (id) {
        PSEUDO : function (pseudo, argument) {
        TAG    : function (nodeNameSelector) {
    }
    

    先看看两个匹配器的源码

    // ID元匹配器工厂
    Expr.filter[ "ID" ] =  function( id ) {
      var attrId = id.replace( runescape, funescape );
      // 生成一个匹配器
      return function( elem ) {
        var node = typeof elem.getAttributeNode !== "undefined" &&
          elem.getAttributeNode( "id" );
        // 判断id是否一致
        return node && node.value === attrId;
      };
    };
    
    // 属性元匹配器工厂
    // name :属性名
    // operator :操作符
    // check : 要检查的值
    // 例如选择器 [type="checkbox"]中,name="type" operator="=" check="checkbox"
    "ATTR": function(name, operator, check) {
        // 返回一个元匹配器
        return function(elem) {
            // 先取出节点对应的属性值
            var result = Sizzle.attr(elem, name);
    
             // 看看属性值有木有!
            if (result == null) {
                // 如果操作符是不等号,返回真,因为当前属性为空 是不等于任何值的
                return operator === "!=";
            }
            // 如果没有操作符,那就直接通过规则了
            if (!operator) {
                return true;
            }
    
            result += "";
    
            // 如果是等号,判断目标值跟当前属性值相等是否为真
            return operator === "=" ? result === check :
               // 如果是不等号,判断目标值跟当前属性值不相等是否为真
                operator === "!=" ? result !== check :
                // 如果是起始相等,判断目标值是否在当前属性值的头部
                operator === "^=" ? check && result.indexOf(check) === 0 :
                // 这样解释: lang*=en 匹配这样 <html lang="xxxxenxxx">的节点
                operator === "*=" ? check && result.indexOf(check) > -1 :
                // 如果是末尾相等,判断目标值是否在当前属性值的末尾
                operator === "$=" ? check && result.slice(-check.length) === check :
                // 这样解释: lang~=en 匹配这样 <html lang="zh_CN en">的节点
                operator === "~=" ? (" " + result + " ").indexOf(check) > -1 :
                // 这样解释: lang=|en 匹配这样 <html lang="en-US">的节点
                operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" :
                // 其他情况的操作符号表示不匹配
                false;
        };
    },
    

    Sizzle.compile
    接下来要做得事情就是对每一个Token序列都生成一个匹配器,然后再将所有匹配器融合成一个大的匹配器

    compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
        var i,
            setMatchers = [],
            elementMatchers = [],
            cached = compilerCache[ selector + " " ];
    
        if ( !cached ) { // 依旧看看有没有缓存
    
            if ( !match ) { // 如果没有词法解析过
                match = tokenize( selector );
            }
            i = match.length;
            // 如果有并联选择器这里多次循环
            while ( i-- ) {
                // 这里用matcherFromTokens来生成对应Token的匹配器
                cached = matcherFromTokens( match[ i ] );
                if ( cached[ expando ] ) {
                    setMatchers.push( cached );
                } else { // 那些普通的匹配器都压入了elementMatchers里边
                    elementMatchers.push( cached );
                }
            }
    
            // 这里可以看到,是通过matcherFromGroupMatchers这个函数来生成最终的匹配器
            cached = compilerCache(
                selector,
                matcherFromGroupMatchers( elementMatchers, setMatchers )
            );
    
            cached.selector = selector;
        }
    
        // 把这个终极匹配器返回到select函数中
        return cached;
    };
    

    matcherFromTokens
    matcherFromTokens才是Sizzle.compile中起主要作用的方法

    // 生成用于匹配单个选择器组的函数
    // 充当了selector“tokens”与Expr中定义的匹配方法的串联与纽带的作用
    // Sizzle没有直接将拿到的“分词”结果与Expr中的方法逐个匹配
    // 而是先根据规则组合出一个大的匹配方法
    function matcherFromTokens( tokens ) {
        var checkContext, matcher, j,
            len = tokens.length,
            leadingRelative = Expr.relative[ tokens[ 0 ].type ],
            implicitRelative = leadingRelative || Expr.relative[ " " ],
            i = leadingRelative ? 1 : 0,
    
            // 这一大段代码是用来确保elem能够在context中找到
            // 以下两个函数中的checkContext都等同于context
            // matchContext:逐层查找elem的parentNode,看看parentNode是否等于context
            matchContext = addCombinator( function( elem ) {
                return elem === checkContext;
            }, implicitRelative, true ),
            // matchAnyContext:用于查找列表(checkContext)中是否包含元素(elem)
            matchAnyContext = addCombinator( function( elem ) {
                return indexOf( checkContext, elem ) > -1;
            }, implicitRelative, true ),
            // 在生成匹配器之前matchers就已经压入了函数
            matchers = [ function( elem, context, xml ) {
                var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
                    // 之前说checkContext等同于context的原因
                    // 判断context.nodeType
                    ( checkContext = context ).nodeType ?
                        // 如果有context是一个节点
                        matchContext( elem, context, xml ) :
                        // 如果有context是一个集合
                        matchAnyContext( elem, context, xml ) );
    
                checkContext = null;
                return ret;
            } ];
    
        // 正片开始
        for ( ; i < len; i++ ) {
            // "空 > ~ +"
            if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { // 当遇到关系选择器时elementMatcher函数将matchers数组中的函数生成一个函数
                matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
            } else { // ATTR CHILD CLASS ID PSEUDO TAG
    
                // 生成对应的匹配器
                matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
    
                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(
    
                        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中
                matchers.push( matcher );
            }
        }
    
        // 将matchers中的函数合成1个函数
        return elementMatcher( matchers );
    }
    

    简单来说就是遇到一般类型的匹配器就压入matchers,遇到关系型匹配器(空 > ~ +)就合并matchers中的函数

    elementMatcher
    将matchers中的函数合并成一个。无非就是将matchers中的函数拿出来一个个执行,全部执行结果为true才返回true,否则返回false

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

    addCombinator
    addCombinator方法就是为了生成有位置词素的匹配器,简单来说就是将elem元素转移成elem.parentNode或者previousSibling再注入到匹配器中。

    function addCombinator( matcher, combinator, base ) {
        var dir = combinator.dir,
            skip = combinator.next,
            key = skip || dir,
            checkNonElements = base && key === "parentNode",
            doneName = done++; // 第几个关系选择器
    
        return combinator.first ?
    
            // 如果是紧密关系的位置词素
            function( elem, context, xml ) {
                while ( ( elem = elem[ dir ] ) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        // 找到第一个亲密的节点,立马就用终极匹配器判断这个节点是否符合前面的规则
                        // 也就循环一次
                        return matcher( elem, context, xml );
                    }
                }
                return false;
            } :
    
            // 如果是不紧密关系的位置词素
            function( elem, context, xml ) {
                var oldCache, uniqueCache, outerCache,
                    newCache = [ dirruns, doneName ];
    
                // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
                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 ] = {} );
    
                            // Support: IE <9 only
                            // Defend against cloned attroperties (jQuery gh-1709)
                            uniqueCache = outerCache[ elem.uniqueID ] ||
                                ( outerCache[ elem.uniqueID ] = {} );
    
                            if ( skip && skip === elem.nodeName.toLowerCase() ) {
                                elem = elem[ dir ] || elem;
                            } else if ( ( oldCache = uniqueCache[ key ] ) &&
                                oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
    
                                // Assign to newCache so results back-propagate to previous elements
                                return ( newCache[ 2 ] = oldCache[ 2 ] );
                            } else {
    
                                // Reuse newcache so results back-propagate to previous elements
                                uniqueCache[ key ] = newCache;
    
                                // 主要的执行部分在这里
                                // 如果不是紧密关系,就一级一级的去匹配
                                if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
                                    return true;
                                }
                            }
                        }
                    }
                }
                return false;
            };
    }
    

    相关文章

      网友评论

        本文标题:jQuery源码#4 Sizzle 预编译

        本文链接:https://www.haomeiwen.com/subject/veqhlhtx.html