天天看點

jQuery1.11源碼分析(5)-----Sizzle編譯和過濾階段[原創]

在上一章中,我們說到在之前的查找階段我們已經獲得了待選集seed,那麼這一章我們就來講如何将seed待選集過濾,以獲得我們最終要用的元素。

其實思路本質上還是不停地根據token過濾,但compile這個函數将這些matcher(filter生成的閉包過濾函數)給編譯成一個函數(這個效率和我們直接使用過濾函數差不多,關鍵是在後面),再儲存這一個函數,以後遇到同樣的selector就可以不用再編譯,直接調用就可以了。

接下來我們看看compile的代碼

compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
    console.log('compile begin');
    console.log('arguments:selector, group');
    console.log(arguments);
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !group ) {
			group = tokenize( selector );
		}
		i = group.length;
		while ( i-- ) {
            console.log('compile matcherFromTokens '+i);
			cached = matcherFromTokens( group[i] );
            console.log('compile after matcherFromTokens '+i);
            console.log([cached]);
			if ( cached[ expando ] ) {
                //這裡的差別是,setMatchers是當有僞類進行過遞歸調用Sizzle時出現的多層次的matcher
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
        console.log('compile matcherFromGroupMatchers');
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
        console.log('compile after matcherFromGroupMatchers');
        console.log([cached]);

	}
	return cached;
};
      

可以看出,compile實際上就是将不同的tokens用matcherFromTokens編譯成一個個matcher(兩種不同的matcher,setMatcher和elementMatcher),最後再調用matcherFromGroupMatchers,生成一個superMatcher。

我們接下來看看matcherFromTokens和matcherFromGroupMatchers的源碼(注意它是在什麼時候把expando加上的,可能還要回到前幾篇去看)

function matcherFromTokens( tokens ) {
    console.log('matcherFromTokens begin');
    console.log('arguments:tokens');
    console.log(arguments);
    console.log('matcherFromTokens addCombinator');
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf.call( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
            console.log('matchers 1 begin');
            console.log('arguments:elem, context, xml');
            console.log(arguments);
			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
            console.log('matcherFromTokens addCombinator '+i);
            console.log('matcherFromTokens addCombinator elementMatcher '+i);
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
            console.log('matcherFromTokens after addCombinator '+i);
            console.log(matchers);
		} else {
            //如果不是連接配接符
            console.log('matcherFromTokens filter '+i);
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
            console.log('matcherFromTokens after filter '+i);
            console.log(matchers);
			// Return special upon seeing a positional matcher
            //expando說明什麼?
            //在上面tokens[i].type為child或者pseudo時,matcher有[expando]
            //是以有expando的時候就要加強處理
			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;
					}
				}
                //prefilter,selector,matcher,postFilter,postFinder,postSelector;
                //先看這裡傳入的參數,對于了解setMatcher非常有幫助,它說明matcherFromTokens用了遞歸的思想,把tokens切割成兩部分,已比對過的和待查找的
                console.log('matcherFromTokens setMatcher');
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(
						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						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.push( matcher );
		}
	}
    console.log('matcherFromTokens elementMatcher');
	return elementMatcher( matchers );
}
      

在matcherFromTokens裡用到了三個函數,addCombinator,setMatcher和elementMatcher,後兩者的差別在前面的注釋中已經有提及了,當不涉及遞歸等操作時,使用的就是普通的elementMatcher和addCombinator,elementMatcher的代碼非常簡單,如下

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,增加一個Combinator類型的matcher

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;
    //有first代表隻檢查第一個元素
	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, outerCache,
            //保證當次dirruns,doneName不變
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from dir 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 ] = {});
                        //這裡的outerCache也就是一個對象啊?怎麼會有dir屬性呢,是後面存進去的
						if ( (oldCache = outerCache[ dir ]) &&
							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
                            //緩存最讓我擔心的還是失效時機
							outerCache[ dir ] = newCache;

							// A match means we're done; a fail means we have to keep checking
                            //這裡我感覺這個函數的結構設計和matcher是緊耦合的。
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
		};
}
      

再來看最長的matcher。。。一定要結合上面調用setMatcher時傳入的參數一起看

//這TM又是一個奇葩的函數。
//傳回一個添加了expando的函數
//讓我難以了解的是filter和matcher這兩種類型的函數有什麼差別,沒差別,filter生成matcher
//第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 );
	}
    //遇到這種閉包函數,要注意對上面參數的使用
    //這裡的seed和results有什麼差別?
    //results用來存已經可以确定傳回的元素
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
            //這裡如果沒有seed,則獲得所有context下的符合selector或*的元素
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
            //這行代碼執行完後,matcherIn裡剩下的元素是elems裡通過preFilter過濾的,preMap存的是過濾通過的元素在原elems裡的序号,從小到大
			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 :
                //如果沒有matcher,matcherOut就是matcherIn
				matcherIn;

		// Find primary matches
		if ( matcher ) {
            //????matcher為什麼會傳入4個參數?看之前的聲明隻有3個
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
        //應用尾過濾
		if ( postFilter ) {
            //這裡temp基本就是不做任何處理拷貝過來啊
			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]) ) {
                    //這個temp果然是中介,把這些沒用到的元素再覆寫到原matcherIn,按照postMap從大到小的順序,再把matcherOut中的這部分設為false
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}
        //如果還剩seed
		if ( seed ) {
			if ( postFinder || preFilter ) {
                //如果有postFinder?
				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.apply( results, matcherOut );
			}
		}
	});
}
      

matcherFromTokens通過調用上面三個函數,然後生成了一個個matchers數組,然後compile再調用matcherFromGroupMatchers把這些matchers合并成一個超級matcher

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
    console.log('matcherFromGroupMatchers begin');
    console.log('arguments:elementMatchers, setMatchers');
    console.log(arguments);
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
            console.log('superMatcher begin');
            console.log('arguments:seed, context, xml, results, outermost');
            console.log(arguments);
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,
				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
				len = elems.length;

			if ( outermost ) {
				outermostContext = context !== document && context;
			}

			// Add elements passing elementMatchers directly to results
			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context, xml ) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
                //???????這裡有什麼用?
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// Apply set filters to unmatched elements
			matchedCount += i;
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
                    console.log('matcherFromGroupMatchers matcher '+j);
					matcher( unmatched, setMatched, context, xml );
                    console.log('matcherFromGroupMatchers after matcher '+j);
                    console.log('setMatched');
                    console.log(setMatched);
                    console.log('unmatched');
                    console.log(unmatched);
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
                    //這裡有毛用啊?
					setMatched = condense( setMatched );
				}

				// Add matches to results
                //注意這裡用的是apply
                //我TM終于了解為什麼會有call和apply這兩種文法了。。
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
                //沒有待選元素了,就可以去除結果裡重複的元素了
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {
                    console.log('matcherFromGroupMatchers uniqueSort');
					Sizzle.uniqueSort( results );
                    console.log('matcherFromGroupMatchers after uniqueSort');
                    console.log(results);
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}
      

嗯,拿到這個superMatcher,剩下的就是調用了,最後我們看看傳進去的參數(在select中調用)

compile( selector, match )(
		seed,
		context,
		!documentIsHTML,
		results,
		rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
      

Sizzle源碼基本就是這樣了,接下來的文章我們會繼續分析jQuery其他子產品。

繼續閱讀