jQuery源码笔记.jpg

jQuery源码#4 Sizzle 预编译

2020-04-15  本文已影响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;
        };
}
上一篇下一篇

猜你喜欢

热点阅读