前言
本篇文章是剖析Sizzle的最后一篇,上一篇文章把大概的匹配器解析过程剖析了,但是还有一个特殊的选择器规则没说到:位置伪类。(姑且称为位置伪类吧,因为它与节点在父节点的位置信息有关)
上一篇文章已经把详细的匹配器工作过程叙述了一遍,本篇文章不再叙述相关过程,只是把其中有关于位置伪类特殊处理的部分剖析一下,了解详细匹配过程移步到《jQuery源码剖析(九)——Sizzle选择器引擎之匹配器》
位置伪类
来看一个选择器“div:first input:checked + p”,其中的:first就表示选取第一个div节点,它就是本文的重点:位置伪类。上一篇文章讲到了,每个子规则都有对应的匹配器,同样道理,位置伪类也有特殊的匹配器,它是由setMatcher工厂生成。
为了区分其他规则跟位置伪类,需要对位置伪类的过滤器匹配器等打个标记。Sizzle源码里边用到的打标记方法:
1 2 3 4 5 6 7 8 | /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } |
首先传入一个正常的过滤器/匹配器函数,然后给这个函数加上一个expando属性,这样就打上了“位置伪类”的标记了。
Sizzle里边定义了以下几种位置伪类(first|last|eq|even|odd|lt|gt),当然你可以自己扩展它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | // Returns a function to use in pseudos for positionals //用来生成位置伪类的过滤器工厂 function createPositionalPseudo( fn ) { return markFunction(function( argument ) { //用markFunction打上“标记” argument = +argument; return markFunction(function( seed, matches ) { //用markFunction打上“标记” var j, //fn是返回位置索引数组,如果是:first对应就是[0]数组 //详情见下 matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { //把对应位置的节点取出来,放到seed里边 if ( seed[ (j = matchIndexes[i]) ] ) { //把对应的节点放到matchers里边去 seed[j] = !(matches[j] = seed[j]); } } }); }); } //这里就是上边的fn的内容。 Expr.pseudos = { //other code... // Position-in-collection "first": createPositionalPseudo(function() { //取第一个孩子节点 return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { //取最后一个孩子节点 return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { //取第argument个孩子 return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { //取偶数位置的孩子 var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { //取奇数位置的孩子 var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { //取小于argument位置的孩子 var i = argument < 0 ? argument + length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { //取大于argument位置的孩子 var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } |
位置伪类匹配器
Sizzle是如何生成一个位置伪类的匹配器呢?在将Token序列转化成匹配器的方法matcherFromTokens里边:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher //注意这里!!!! if ( matcher[ expando ] ) {//如果含有位置伪类的选择器 // Find the next relative operator (if any) for proper handling j = ++i; //找到下个位置(+~>空格)的索引 for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } //看到没?是通过setMatcher来生成匹配器的! //还是这个例子:div:first input:checked + p //这里的位置伪类就是:first,以他为分界线,用setMatcher来处理 return setMatcher( //第1个参数,preFilter,前置过滤器,相当于“div”过滤器 i > 1 && elementMatcher( matchers ), //第2个参数,selector,前置过滤器的字符串格式,相当于“div”input:checked + p i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), //第3个参数,matcher,当前位置伪类“:first”的匹配器/过滤器 matcher, //第4个参数,postFilter,后置过滤器,相当于“ ” i < j && matcherFromTokens( tokens.slice( i, j ) ), //第5个参数,postFinder,后置搜索器,相当于在前边过滤出来的集合里边再搜索剩下的规则的一个搜索器 j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), //第6个参数,postSelector,后置搜索器对应的选择器字符串,相当于“input:checked + p” j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } |
从上边的例子很容易就想到,我们首先应该先把所有的div选取出来,然后通过一个“位置过滤器”把first个div节点取出来做剩下规则的一个seed集合。通过通过后置的一个搜索器跟过滤器来过滤出结果集。
流程图如下:
对应的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | //还是这个例子:div:first input:checked + p //第1个参数,preFilter,前置过滤器,相当于“div”过滤器 //第2个参数,selector,前置过滤器的字符串格式,相当于“div”input:checked + p //第3个参数,matcher,当前位置伪类“:first”的匹配器/过滤器 //第4个参数,postFilter,后置过滤器,相当于“ ” //第5个参数,postFinder,后置搜索器,相当于在前边过滤出来的集合里边再搜索剩下的规则的一个搜索器 //第6个参数,postSelector,后置搜索器对应的选择器字符串,相当于“input:checked + p” function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } //工厂方法,生成一个打上标记的匹配器! return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context //取出种子集合seed //这个函数是递归调用Sizzle构造函数来寻找DOM结果集 elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization //前置过滤出来的DOM集合 //condense函数不剖析了 matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches //通过刚刚createPositionalPseudo生成的位置过滤器 //把matcherIn过滤出matcherOut if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } //这个时候matcherOut对应的是规则“div:first ”找到的DOM节点 //接着从matcherOut中用后置规则“ input:checked + p”过滤! // Apply postFilter //先用postFilter过滤出候选集 if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } //用后置搜索器从候选集筛出结果 if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { //如果有后置搜索器,用它搜索结果集 postFinder( null, results, matcherOut, xml ); } else { //否则,直接就是push到结果集 push.apply( results, matcherOut ); } } }); } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { //调用Sizzle构造器搜索结果集 Sizzle( selector, contexts[i], results ); } return results; } |
至此,Sizzle的源码(1891行代码)剖析完毕,jQuery用Sizzle选择器引擎取出DOM节点后,还自己定义了一套操作DOM节点的方法,例如取节点的属性(attr接口)、移除节点(remove接口)、动画。接下去的文章就是剖析jQuery这些接口,当中有不少涉及到浏览兼容的问题。
本文链接:jQuery源码剖析(十)——Sizzle选择器引擎之位置伪类
转载声明:本博客文章若无特别说明,皆为原创,转载请注明来源:拉风的博客,谢谢!