jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)

  我们知道Sizzle支持的伪类有有很多,为了便于处理被Sizzle分成了两类:单个单词的伪类“PSEUDO”和多个词语使用“-”链接的“CHILD”。我们下面一一分析。

 

  先看"PSEUDO":

a. Sizzle的伪类选择器"PSEUDO"

 

  我们先整体看一下有哪些个伪类

  jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)_第1张图片

  伪类生成匹配器的源码如下:

matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

  此时tokens[i].type = “PSEUDO”,所以生成伪类匹配器函数是Expr.filter["PSEUDO"]函数

                "PSEUDO": function( pseudo, argument ) {
                    // 伪类名称大小写不敏感
                    // 优先Expr.pseudos找到伪类对应的位置伪类函数,不行的话将伪类名称转换成小写形式使用Expr.setFilters
                    // 记住setFilters继承至Expr.pseudos
                    var args,
                        fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
                        Sizzle.error( "unsupported pseudo: " + pseudo );

                    // 用户也许使用createPseudo来表明参数需要创建过滤函数
                    // 类似Sizzle的做法
                    if ( fn[ expando ] ) {
                        return fn( argument );
                    }

                    //但是,保持老的签名支持(兼容老版本)
                    if ( fn.length > 1 ) {
                        args = [ pseudo, pseudo, "", argument ];
                        return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
                        markFunction(function( seed, matches ) {
                            var idx,
                                matched = fn( seed, argument ),
                                i = matched.length;
                            while ( i-- ) {
                                idx = indexOf.call( seed, matched[i] );
                                seed[ idx ] = !( matches[ idx ] = matched[i] );
                            }
                        }) :
                        function( elem ) {
                            return fn( elem, 0, args );
                        };
                    }

                    return fn;
                }

 

  Sizzle在处理这一系列的伪类过程中将这些处理分为几个组来处理(分组方案使得每组都有相同点能使用共同代码,节约代码量,值得学习)。

  第一组,将那些易于判断节点是否匹配的伪类放在一组,这一组包括的伪类有"target"/"root"/"focus"/"enabled"/"disabled"/"checked"/"selected"/"empty"/ "parent"/"header"/"input"/"button"/"text",这类原子匹配器在Expr.filter["PSEUDO"]中的处理简化如下

//pseudo表示伪类选择器,比如"target"
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo );
...
return fn;

  Expr.pseudos[ pseudo ]即是对应的原子匹配器。。

  看一个例子“target”,target选择器用于匹配id属性值等于当前文档URI的锚点名称的元素如果当前文档的URI为http://www.365mini.com/#top,则:target选择器匹配id="top"的元素。

  Expr.pseudos["target"]的源码如下

                "target": function( elem ) {
                    var hash = window.location && window.location.hash;
                    return hash && hash.slice( 1 ) === elem.id;
                }

  剔除掉第一组伪类后剩余的伪类如下图

  jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)_第2张图片

 

  第二组,伪类带有一个参数非位置伪类,这类伪类有"contains"/"has"/"not"/"lang"。比如“:not(.chua)”。这类原子匹配器在Expr.filter["PSEUDO"]中的处理为

fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo );

// 例如Expr.pseudo["not"]使用markFunction将fn[expando]标记为true // 类似Sizzle的做法 if ( fn[ expando ] ) {   return fn( argument ); }

   以"not"为例,Expr.pseudo["not"]源码如下

pseudos: {
    // Potentially complex pseudos
    "not": markFunction(function( selector ) {
        // selector参数需要去除前后的空白,避免被识别为关系选择器
        // 先生成selector的匹配器放在matcher中
        var input = [],
            results = [],
            matcher = compile( selector.replace( rtrim, "$1" ) );

        return matcher[ expando ] ?
        //如果selector中有伪类,返回的函数是伪类分割器方式返回的匹配器,
        //其中matches对应着results
        markFunction(function( seed, matches, context, xml ) {
            //先把种子seed中能匹配matcher的元素存在unmatched备用
            var elem,
                unmatched = matcher( seed, null, xml, [] ),
                i = seed.length;

                // 将unmatched里面的元素取出来存在matches并同步seed
                while ( i-- ) {
                    if ( (elem = unmatched[i]) ) {
                        seed[i] = !(matches[i] = elem);
                    }
                }
            }) :
        //selector中没有伪类
        function( elem, context, xml ) {
            input[0] = elem;
            matcher( input, null, xml, results );
            return !results.pop();
        };
    })

  ":not(selector)"处理比正常的情况复杂一些,多了一种当selector中包含伪类的特殊处理(当selector没有伪类的时候返回的匹配器和正常情况没有太多区别)。在selector有伪类的时候按照伪类的方式来处理,返回的匹配器有四个参数是function( seed, results, context, xml ) ,匹配结果保存在第二个参数results中。  

  继续剔除掉第二组伪类后剩余的伪类如下图

  jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)_第3张图片

 

  第三组,位置伪类,这一组包括"first"/"last"/"eq"/"even"/"odd"/"lt"/"gt",其中:eq(index)、:gt(index)、:lt(index)这三个伪类需要一个index参数。

这类原子匹配器在Expr.filter["PSEUDO"]中的处理为

"PSEUDO": function( pseudo, argument ) {
  ...
  fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo );
  // 例如Expr.pseudo["not"]使用markFunction将fn[expando]标记为true   // 类似Sizzle的做法   if ( fn[ expando ] ) {     return fn( argument );   }
  ...
}

  根据伪类匹配器生成的方法(在matcherFromTokens函数中)

matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches )

  Expr.filter["PSEUDO"]的参数( pseudo, argument )和tokens[i].matches对应。

  我们看一下Expr.pseudo["eq"]的方法源码:

pseudos: {
    "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
        return [ argument < 0 ? argument + length : argument ];
    })
    ...
}

// 返回一个函数给位置伪类使用
function createPositionalPseudo( fn ) {
    return markFunction(function( argument ) {
        argument = +argument;
     //标记匹配器为类似伪类分隔器生成的匹配器(这类匹配器都有四个参数( seed, results, context, xml ))
return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // 匹配指定下标的元素 while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); }

  以“eq(0)”为例,Expr.filter["PSEUDO"]的参数( pseudo = "eq", argument = 0 )【tokens[i].matches = ["eq","0"]】。那么对应着Expr.pseudo["eq"]的参数为(argument = 0),即createPositionalPseudo中argument = 0。最终使用markFunction将匹配器标记为类似伪类分隔器生成的匹配器(这类匹配器都有四个参数( seed, results, context, xml ))。

 

  继续剔除第三组后剩下的伪类如下图

  jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)_第4张图片

  剩下的所有的为第四组伪类,他们针对input标签做匹配。他们是有以下的代码生成的

        // Add button/input type pseudos
        for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
            Expr.pseudos[ i ] = createInputPseudo( i );
        }
        for ( i in { submit: true, reset: true } ) {
            Expr.pseudos[ i ] = createButtonPseudo( i );
        }

        // Returns a function to use in pseudos for input types
        function createInputPseudo( type ) {
            return function( elem ) {
                var name = elem.nodeName.toLowerCase();
                return name === "input" && elem.type === type;
            };
        }

        // Returns a function to use in pseudos for buttons
        function createButtonPseudo( type ) {
            return function( elem ) {
                var name = elem.nodeName.toLowerCase();
                return (name === "input" || name === "button") && elem.type === type;
            };
        }

  这些个伪类比较简单,不再详解。当然应为Sizzle被jQuery内置,jQuery本身还拓展了部分这样的伪类,比如:visible/hidden/animated

 

b. Sizzle伪类选择器中的子伪类选择器“CHILD”

  在词法分析的时候就提到过有三种比较特别的词语ATTR/CHILD/PSEUDO,他们在词法分析完成以后还需要做一下额外的工作修饰他们的matches字段。这里主要讲CHILD。

  以":nth-of-type(2)"为例,词法分析中先通过正则使用exec方法得到的结果如下

  RegExpObj.exec(":nth-of-type(2)"):  [":nth-of-type(2)", "nth", "of-type", "2", "", undefined, undefined, "", "2"]

  使用preFilters修正后结果为

  match = preFilters["CHILD"]( match ): [":nth-of-type(2)", "nth", "of-type", "2", 0, 2, undefined, "", "2"]

  最终这个CSS选择器词法分析完成后结果为

//CSS选择器":nth-of-type(2)"词法解析结果tokens
tokens = [{
  matches: ["nth","of-type","2",0,2,undefined,"","2"],
  type: "CHILD",
  value: ":nth-of-type(2)"
}]

  同先前的只有一个单词的伪类选择器一样,“CHILD”方式获取匹配器的方法如下(在matcherFromTokens函数中)

matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches )

  Expr.filter["CHILD"]的参数(type, what, argument, first, last)和tokens[i].matches对应。以":nth-of-type(2)"为例,则为(type ="nth", what = "of-type", argument = "2", first = 0, last = 2)

  好了,有这个认识就行。在讲解源码之前我们来看一下"CHILD"伪类对应的都有哪些

  • :first-child
  • :last-child
  • :only-child
  • :first-of-type
  • :last-of-type
  • :only-of-type
  • :nth-child(n)
  • :nth-last-child(n)
  • :nth-of-type(n)
  • :nth-last-of-type(n)

  前面几个没有参数n为简单子伪类选择器,后面几个为复杂子伪类选择器。所以处理的时候也是分成两组来处理的。

 

  在编译生成匹配器的阶段,子匹配器(“CHILD”)对几个重要的状态进行了初始化,以备执行匹配的时候使用

  var simple = type.slice( 0, 3 ) !== "nth",//简单子选择器(以非nth开头的6个选择器)?
    forward = type.slice( -4 ) !== "last",//向前查找(以非last开头的选择器都是循环查找nextSibling来处理的,唯有以last开头的选择器是使用previousSibling来处理?
    ofType = what === "of-type";//*-of-type?
选择器 simple forward ofType
first-child【type为"first",what为"child"】 true true false
last-child【type为"last",what为"child"】 true false false
only-child【type为"only",what为"child"】 true true false
first-of-type【type为"first",what为"of-type"】 true    true   false  
last-of-type【type为"last",what为"of-type"】 true false true
only-of-type【type为"only",what为"of-type"】 true true true
nth-child(n)【type为"nth",what为"child"】 false true false
nth-last-child(n)【type为"nth-last",what为"child"】 false false false
nth-of-type(n)【type为"nth",what为"of-type"】 false true true
nth-last-of-type(n)【type为"nth-last",what为"of-type"】 false false true

  

  在执行阶段简单子伪类选择器比较简单,比如first-child,dir(dir = simple !== forward ? "nextSibling" : "previousSibling")为"previousSibling",只要不断比较node = node[ dir ]判断node是否还有"previousSibling"节点,是表示该节点并不是first-child,返回false;否表示该节点就是first-child,返回true。需要特别处理的是only-chlid需要双向比较。源码

                            // :(first|last|only)-(child|of-type)
                            if ( simple ) {
                                while ( dir ) {
                                    node = elem;
                                    while ( (node = node[ dir ]) ) {
                                        if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
                                            return false;
                                        }
                                    }
                                    // Reverse direction for :only-* (if we haven't yet done so)
                                    start = dir = type === "only" && !start && "nextSibling";
                                }
                                return true;
                            }

  复杂子伪类选择器的处理分成几个分支来处理,结构如下

                            start = [ forward ? parent.firstChild : parent.lastChild ];

                            // non-xml :nth-child(...) 保存缓存数据到其父节点
                            if ( forward && useCache ) {
                                ...// 使用以前缓存元素索引(如果可用的话)
                            } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
                                diff = cache[1];

                                // xml :nth-child(...) 或 :nth-last-child(...) 或 :nth(-last)?-of-type(...)
                            } else {
                                ...
                            }

                            // Incorporate the offset, then check against cycle size
                            diff -= last;
                            return diff === first || ( diff % first === 0 && diff / first >= 0 );

  具体代码分析了,肚子饿了,回家。。。

  

   如果觉得本文不错,请点击右下方【推荐】! 

你可能感兴趣的:(jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解))