jQuery1.11ソース分析(5)----Sizzleコンパイルとフィルタリング段階[オリジナル]
17987 ワード
前章では、前の検索段階で選択対象セットseedを取得したと述べ、この章では、最終的に使用する要素を得るためにseed選択対象セットをフィルタする方法について説明します.
実は考え方は本質的にtokenに基づいてフィルタリングを続けていますが、compileという関数はこれらのmatcher(filterが生成した閉パケットフィルタ関数)を1つの関数にコンパイルし(この効率は私たちが直接フィルタ関数を使うのとあまり差がなく、肝心なのは後です)、この関数を保存して、後で同じselectorに出会ったらコンパイルしなくても、直接呼び出すことができます.
次にcompileのコードを見てみましょう
compileは実際には異なるtokensをmatcherFromTokensで1つのmatcher(2つの異なるmatcher,setMatcher,elementMatcher)にコンパイルし,最後にmatcherFromGroupMatchersを呼び出してsuperMatcherを生成することを示している.
次にmatcherFromTokensとmatcherFromGroupMatchersのソースコードを見てみましょう(いつexpandoを追加したのか注意して、前の編に戻って見るかもしれません)
matcherFromTokensでは3つの関数,addCombinator,setMatcher,elementMatcherを用いたが,後者の違いは前述の注釈で既に言及されており,再帰などの操作に関与しない場合には通常のelementMatcherとaddCombinatorを用いるが,elementMatcherのコードは非常に簡単で,以下のようになる.
短い関数addCombinatorを見て、Combinatorタイプのmatcherを追加します.
一番長いmatcherを見てみましょう...必ず上のsetMatcher呼び出し時に入ってくるパラメータと合わせて見てください
matcherFromTokensは上の3つの関数を呼び出すことで、matchers配列を生成し、compileはmatcherFromGroup Matchersを呼び出してこれらのmatchersをスーパーmatcherに結合します.
はい、このsuperMatcherを手に入れて、残りは呼び出しです.最後に、転送されたパラメータ(selectで呼び出されます)を見てみましょう.
Sizzleソースコードは基本的にそうですが、次の文章ではjQueryの他のモジュールを分析し続けます.
実は考え方は本質的にtokenに基づいてフィルタリングを続けていますが、compileという関数はこれらのmatcher(filterが生成した閉パケットフィルタ関数)を1つの関数にコンパイルし(この効率は私たちが直接フィルタ関数を使うのとあまり差がなく、肝心なのは後です)、この関数を保存して、後で同じ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で1つのmatcher(2つの異なる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では3つの関数,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は上の3つの関数を呼び出すことで、matchers配列を生成し、compileはmatcherFromGroup Matchersを呼び出してこれらの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の他のモジュールを分析し続けます.