jQuery源码笔记.jpg

jQuery源码#2 Sizzle 词法解析

2020-04-15  本文已影响0人  柠檬果然酸

本节内容
1.Sizzle.tokenize

Sizzle引擎
这是一个CSS选择器引擎,用于解析像div > p + .aaron[type="checkbox", display="none"], #id:first-child这样的CSS选择器。

从右到左解析选择器
因为这样效率更高(从右边查询一开始锁定的范围就很小)。

词法分析
js解析器把脚本代码的字符流转换成记号流,这个记号流就是Token序列

Token: {  
   value: '匹配到的字符串', 
   type: '对应的Token类型', 
   matches: '正则匹配到的一个结构'
}

然后再用这个Token序列去做各种各样的事

Sizzle.tokenize
将字符串的CSS选择器解析成Token序列

// 假设传入进来的选择器是:div > p + .aaron[type="checkbox"], #id:first-child
// 解析器会以","为分隔符,将字符串分为两个部分解析
// 返回Token序列
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {

    // soFar表示字符串未解析的部分
    // groups存放已经解析好的Token序列
    var matched, match, tokens, type,
        soFar, groups, preFilters,
        cached = tokenCache[ selector + " " ];

    if ( cached ) {
        return parseOnly ? 0 : cached.slice( 0 );
    }

    soFar = selector;
    groups = [];
    preFilters = Expr.preFilter;

    // 循环检测字符串
    while ( soFar ) {

        // 检查是否有","
        if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
            if ( match ) { // 如果有就清除","
                soFar = soFar.slice( match[ 0 ].length ) || soFar;
            }

            // 因为一开始matched == undefined,所以注定要往groups里面压入一个空数组(Token序列)
            groups.push( ( tokens = [] ) );
        }

        matched = false;

        // 处理特殊的Token:>, +, 空格, ~
        if ( ( match = rcombinators.exec( soFar ) ) ) {
            matched = match.shift();
            tokens.push( {
                value: matched,
                type: match[ 0 ].replace( rtrim, " " )
            } );

            // 处理完之后将其从待处理的字符中删除
            soFar = soFar.slice( matched.length );
        }

        // 这里开始分析这几种Token:TAG, ID, CLASS, ATTR, CHILD, PSEUDO, NAME
        // 如果通过正则匹配到了Token格式:match = matchExpr[ type ].exec( soFar )
        // 然后看看需不需要预处理:!preFilters[ type ]
        // 如果需要,那么通过预处理器将匹配到的处理一下:match = preFilters[ type ]( match )
        for ( type in Expr.filter ) {
            if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
                ( match = preFilters[ type ]( match ) ) ) ) {
                matched = match.shift();
                tokens.push( {
                    value: matched,
                    type: type,
                    matches: match
                } );

                // 处理完之后将其从待处理的字符中删除
                soFar = soFar.slice( matched.length );
            }
        }

        // 如果到了这里都还没matched到,那么说明这个选择器在这里有错误
        // 直接中断词法分析过程
        // 这就是Sizzle对词法分析的异常处理
        if ( !matched ) {
            break;
        }
    }

    // 如果只需要这个接口检查选择器的合法性,直接就返回soFar的剩余长度,因为soFar长度大于零说明选择器不合法
    // 其余情况,如果soFar不等于"",抛出异常;否则把groups记录在cache里边并返回
    return parseOnly ?
        soFar.length :
        soFar ?
            Sizzle.error( selector ) :

            // 放到tokenCache函数里进行缓存
            tokenCache( selector, groups ).slice( 0 );
};

通过注释,这个方法的结构变得更简单了。最难理解的地方是matchExpr里的正则表达式,列几个出现较多的正则表达式

空白符
whitespace = "[\\x20\\t\\r\\n\\f]"

符号 意义
\x20 空格
\t 制表符
\r 回车
\n 换行
\f 分页符

matchExpr['CLASS']
/^\.((?:\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\[^\r\n\f]|[\w-]|[^-\x7f])+)/
匹配className的正则表达式,将其拆分成三个部分

\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?
后面部分又出现了匹配空白符的正则表达式

\\[^\r\n\f]
除开回车、换行、分页的所有字符

[\w-]
\w是a-zA-Z0-9_的简写

[^-\x7f]
ASCII码中没查到\x7f,所以作用也不得而知·

上一篇 下一篇

猜你喜欢

热点阅读