[toc]
## compile
讲了这么久的 Sizzle,总感觉差了那么一口气,对于一个 selector,我们把它生成 tokens,进行优化,优化的步骤包括去头和生成 seed 集合。对于这些种子集合,我们知道最后的匹配结果是来自于集合中的一部分,似乎接下来的任务也已经明确:对种子进行过滤(或者称其为匹配)。
匹配的过程其实很简单,就是对 DOM 元素进行判断,而且弱是那种一代关系(>)或临近兄弟关系(+),不满足,就结束,若为后代关系(space)或者兄弟关系(~),会进行多次判断,要么找到一个正确的,要么结束,不过仍需要考虑回溯问题。
比如div > div.seq h2 ~ p,已经对应的把它们划分成 tokens,如果每个 seed 都走一遍流程显然太麻烦。一种比较合理的方法就是对应每个可判断的 token 生成一个闭包函数,统一进行查找。
Expr.filter 是用来生成匹配函数的,它大概长这样:
```
Expr.filter = {
"ID": function(id){...},
"TAG": function(nodeNameSelector){...},
"CLASS": function(className){...},
"ATTR": function(name, operator, check){...},
"CHILD": function(type, what, argument, first, last){...},
"PSEUDO": function(pseudo, argument){...}
}
```
看两个例子,一切都懂了:
```
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
//这里返回一个函数
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
```
对 tokens 分析,让发现其 type 为 ID,则把其 id 保留,并返回一个检查函数,参数为 elem,用于判断该 DOM 的 id 是否与 tokens 的 id 一致。这种做法的好处是,编译一次,执行多次。
那么,是这样的吗?我们再来看看其他例子:
```
Expr.filter["TAG"] = function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
//返回一个函数
function() { return true; } :
// 参数为 elem
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
};
Expr.filter["ATTR"] = function( name, operator, check ) {
// 返回一个函数
return function( elem ) {
var result = Sizzle.attr( elem, name );
if ( result == null ) {
return operator === "!=";
}
if ( !operator ) {
return true;
}
result += "";
return operator === "=" ? result === check :
operator === "!=" ? result !== check :
operator === "^=" ? check && result.indexOf( check ) === 0 :
operator === "*=" ? check && result.indexOf( check ) > -1 :
operator === "$=" ? check && result.slice( -check.length ) === check :
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
false;
};
},
```
最后的返回结果:
- `input[type=button]` 属性 type 类型为 button;
- `input[type!=button]` 属性 type 类型不等于 button;
- `input[name^=pre]` 属性 name 以 pre 开头;
- `input[name*=new]` 属性 name 中包含 new;
- `input[name$=ed]` 属性 name 以 ed 结尾;
- `input[name=~=new]` 属性 name 有用空格分离的 new;
- `input[name|=zh]` 属性 name 要么等于 zh,要么以 zh 开头且后面有关连字符 -。
所以对于一个 token,即生成了一个闭包函数,该函数接收的参数为一个 DOM,用来判断该 DOM 元素是否是符合 token 的约束条件,比如 id 或 className 等等。如果将多个 token (即 tokens)都这么来处理,会得到一个专门用来判断的函数数组,这样子对于 seed 中的每一个元素,就可以用这个函数数组对其父元素或兄弟节点挨个判断,效率大大提升,即所谓的编译一次,多次使用。
## compile 源码
直接贴上 compile 函数代码,这里会有 matcherFromTokens 和 matcherFromGroupMatchers 这两个函数,也一并介绍了。
```
var compile = function(selector, match) {
var i,setMatchers = [],elementMatchers = [],
cached = compilerCache[selector + " "];
// 判断有没有缓存,好像每个函数都会判断
if (!cached) {
if (!match) {
// 判断 match 是否生成 tokens
match = tokenize(selector);
}
i = match.length;
while (i--) {
// 这里将 tokens 交给了这个函数
cached = matcherFromTokens(match[i]);
if (cached[expando]) {
setMatchers.push(cached);
} else {
elementMatchers.push(cached);
}
}
// 放到缓存
cached = compilerCache(
selector,
// 这个函数生成最终的匹配器
matcherFromGroupMatchers(elementMatchers, setMatchers)
);
// Save selector and tokenization
cached.selector = selector;
}
return cached;
};
```
编译 compile 函数貌似很简单,来看 matcherFromTokens:
```
function matcherFromTokens(tokens) {
var checkContext,matcher,j,len = tokens.length,leadingRelative = Expr.relative[tokens[0].type],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0,
// 确保元素都能找到
// addCombinator 就是对 Expr.relative 进行判断
/*
Expr.relative = {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
};
*/
matchContext = addCombinator(
function(elem) {
return elem === checkContext;
},implicitRelative,true),
matchAnyContext = addCombinator(
function(elem) {
return indexOf(checkContext, elem) > -1;
},implicitRelative,true),
matchers = [
function(elem, context, xml) {
var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml));
// Avoid hanging onto element (issue #299)
checkContext = null;
return ret;
}
];
for (; i < len; i++) {
// 处理 "空 > ~ +"
if (matcher = Expr.relative[tokens[i].type]) {
matchers = [addCombinator(elementMatcher(matchers), matcher)];
} else {
// 处理 ATTR CHILD CLASS ID PSEUDO TAG,filter 函数在这里
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);
// Return special upon seeing a positional matcher
// 伪类会把selector分两部分
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;
}
}
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);
}
}
return elementMatcher(matchers);
}
```
其中 addCombinator 函数用于生成 curry 函数,来解决 Expr.relative 情况:
```
function addCombinator(matcher, combinator, base) {
var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++;
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);
}
}
return false;
} : // Check against all ancestor/preceding elements
function(elem, context, xml) {
var oldCache, uniqueCache, outerCache, newCache = [dirruns, doneName];
// We can't set arbitrary data on XML nodes, so they don't benefit from combinator 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] = {});
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {});
if (skip && skip === elem.nodeName.toLowerCase()) {
elem = elem[dir] || elem;
} else if ((oldCache = uniqueCache[key]) && 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
uniqueCache[key] = newCache;
// A match means we're done; a fail means we have to keep checking
if (newCache[2] = matcher(elem, context, xml)) {
return true;
}
}
}
}
}
return false;
};
}
```
其中 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];
}
matcherFromGroupMatchers 如下:
function matcherFromGroupMatchers(elementMatchers, setMatchers) {
var bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function(seed, context, xml, results, outermost) {
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 || outermost;
}
// Add elements passing elementMatchers directly to results
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
for (; i !== len && (elem = elems[i]) != null; i++) {
if (byElement && elem) {
j = 0;
if (!context && elem.ownerDocument !== document) {
setDocument(elem);
xml = !documentIsHTML;
}
while (matcher = elementMatchers[j++]) {
if (matcher(elem, context || document, 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);
}
}
}
// `i` is now the count of elements visited above, and adding it to `matchedCount`
// makes the latter nonnegative.
matchedCount += i;
// Apply set filters to unmatched elements
// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
// no element matchers and no seed.
// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
// case, which will result in a "00" `matchedCount` that differs from `i` but is also
// numerically zero.
if (bySet && i !== matchedCount) {
j = 0;
while (matcher = setMatchers[j++]) {
matcher(unmatched, setMatched, context, xml);
}
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
push.apply(results, setMatched);
// Seedless set matches succeeding multiple successful matchers stipulate sorting
if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) {
Sizzle.uniqueSort(results);
}
}
// Override manipulation of globals by nested matchers
if (outermost) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
}
return unmatched;
};
return bySet ? markFunction(superMatcher) : superMatcher;
}
```
这个过程太复杂了,请原谅我无法耐心的看完。。。
先留名,以后分析。。。
到此,其实已经可以结束了,但我本着负责的心态,我们再来理一下 Sizzle 整个过程。
Sizzle 虽然独立出去,单独成一个项目,不过在 jQuery 中的代表就是 jQuery.find 函数,这两个函数其实就是同一个,完全等价的。然后介绍 tokensize 函数,这个函数的被称为词法分析,作用就是将 selector 划分成 tokens 数组,数组每个元素都有 value 和 type 值。然后是 select 函数,这个函数的功能起着优化作用,去头去尾,并 Expr.find 函数生成 seed 种子数组。
后面的介绍就马马虎虎了,我本身看的也不少很懂。compile 函数进行预编译,就是对去掉 seed 后剩下的 selector 生成闭包函数,又把闭包函数生成一个大的 superMatcher 函数,这个时候就可用这个 superMatcher(seed) 来处理 seed 并得到最终的结果。
那么 superMatcher 是什么?
## superMatcher
前面就已经说过,这才是 compile()() 函数的正确使用方法,而 compile() 的返回值即 superMatcher,无论是介绍 matcherFromTokens 还说介绍 matcherFromGroupMatchers,其结果都是为了生成超级匹配,然后处理 seed,这是一个考验的时刻,只有经得住筛选才会留下来。
## 总结
下面是别人总结的一个流程图:
![](http://olphpb1zg.bkt.clouddn.com/22100009-fb059131d1ed49519db4810fd8a4f20b.jpg)
**第一步**
```
div > p + div.aaron input[type="checkbox"]
```
从最右边先通过 Expr.find 获得 seed 数组,在这里的 input 是 TAG,所以通过 getElementsByTagName() 函数。
**第二步**
重组 selector,此时除去 input 之后的 selector:
```
div > p + div.aaron [type="checkbox"]
```
**第三步**
此时通过 Expr.relative 将 tokens 根据关系分成紧密关系和非紧密关系,比如 [">", "+"] 就是紧密关系,其 first = true。而对于 [" ", "~"] 就是非紧密关系。紧密关系在筛选时可以快速判断。
matcherFromTokens 根据关系编译闭包函数,为四组:
```
div >
p +
div.aaron
input[type="checkbox"]
```
编译函数主要借助 Expr.filter 和 Expr.relative。
**第四步**
将所有的编译闭包函数放到一起,生成 superMatcher 函数。
```
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
}
```
从右向左,处理 seed 集合,如果有一个不匹配,则返回 false。如果成功匹配,则说明该 seed 元素是符合筛选条件的,返回给 results。