這篇文章我會將
Sizzle
整個篩選元素的流程所有講解一遍。從它是如何找出種子集seed
,又是如何將token
轉換爲篩選規則,再到是如何經過規則進行篩選的全部流程。這裏我會經過一個例子來進行說明,由token
轉換爲篩選規則那裏很是的繞,尤爲是Sizzle
還有緩存的邏輯夾雜在其中,並且最複雜的實際上是緩存。我我的的描述可能並不能讓人聽得很明白,因此有興趣的人,能夠結合個人說明去看一下源碼,我到如今也是隻看懂了百分之八十多,緩存的相關代碼我並無理解的特別透徹,因此這裏我只給你們分析一下我本身所理解到的,整個選擇元素的主流程。javascript
Sizzle
並非從左向右依次進行選擇的,並非先選擇出'.container'
而後再去找其下的input
。這樣雖然看似合理,但實際上是很消耗時間的,由於根據DOM
樹的結構越往下分支越多,因此Sizzle
會先在選擇器的末尾找到一個種子集(也就是seed
),而後經過種子集一層一層往上判斷,是否符合條件。vue
那麼如何選擇seed
呢?這就是select
函數乾的事情了。java
這個函數,主要就作了兩件事。node
tokenize
seed
一個選擇字符串可能會存在多個關係選擇器,好比body p>input:disabled
。若是使用這些關係選擇器來做爲分割,咱們能夠獲得幾組選擇器,seed就是在最後一組選擇器中的元素選擇器, ID選擇器, 或者class選擇器
,若是當最後一組選擇器沒有這三個選擇器的話,那麼就沒有seed
。jquery
以上述例子爲例,seed
就是整個document
中的全部input
。數組
若是在setDocument的時候, support.getElementsByClass = false得話,那麼`seed`不包括class選擇器 緩存
select = Sizzle.select = function(selector, context, results, seed) {
var i, tokens, token, type, find,
compiled = type selector === 'function' && selector,
match = !seed && tokenize( (selector = compiled.selector || selector) );
results = results || [];
// 這裏指選擇字符串沒有逗號的狀況,
if (match.length === 1) {
tokens = match[0] = match[0].slice(0);
if (tokens.length > 2 && documentIsHTML && Expr.relative[tokens[1].type] ) {
context = (Expr.find["ID"](token.matches[0]
.replace(runescape, funescape), context) || [])[0];
if (!context) {
return results;
} else if (compiled) {
context = context.parentNode
}
selector = selector.silce(tokens.shift().value.length);
}
i = matchExpr['needsContxt'].test(selector) ? 0 : tokens.length;
// 這裏開始找seed
while (i--) {
token = tokens[i];
// 從後向前, 若是碰到關係選擇器了,那就不找了
if (Expr.relative[(type = token.type)]) {
break;
}
// Expr.find 最多隻有三個屬性,這個是在setDocument的時候設置的
// TAG CLASS ID
if ((find = Expr.find[type])) {
if ( (seed = find(
token.matches[0].replace(runescape, funescape),
rsibling.test(tokens[0].type) && testContext(context.parentNode) ||
context
) ) ) {
tokens.splice(i, 1);
// 因爲已經抽出了seed 因此要重組selector
// 上面的例子跑到這裏 selector就會變成 '.container [type=text]'
selector = seed.length && toSelector(tokens);
if (!selector) {
push.apply(results, seed);
return results
}
break;
}
}
}
}
(compiled || compile(selecotr, match)) (
seed,
context,
!documentIsHTML,
results,
!context || rsibling.test(selector) && testContext(context.parentNode) || context
);
// 注意: 這裏並無return compile返回出來的閉包執行後的結果, 而是return 做爲參數穿進去的results
return results;
}
複製代碼
compile
其實並非生成規則的函數,它算是一個總入口,主要的功能是將生成的規則緩存,從緩存中查找是否已經有對應的規則,返回一個superMatch函數, superMatch函數是作篩選的函數。閉包
compile = Sizzle.compile = function(selector, match) {
var i,
setMatcher = [],
elementMatchers = [],
cached = compilerCache[selector + ' '];
if (!cache) {
if (!match) {
match = tokenize(selector);
}
i = match.length;
// 注意: 這裏的match是整個二維數組, 是整個一個選擇組, 因此這裏只循環一次
while(i--) {
cached = matcherFromTokens(match[i]);
// 在複雜的選擇器的時候, 僞類函數會被標記, 這裏就是判斷是不是僞類
if (cached[expando]) {
setMatchers.push(cached);
} else {
elementMatchers.push(cached);
}
}
}
// 緩存
cache = compilerCache(
selector,
// 這個函數返回superMatch函數
matcherFromGroupMatchers(elementMatchers, setMatchers)
)
}
複製代碼
matcherFromTokens
會經過token
生成規則,流程是這樣的。它會先建立一個matchers
數組,並建立一個baseMathcer
函數,這個baseMatcher
通常狀況都爲true
。以後遍歷整個token
,只要沒有遇到關係操做符,就將對應的filter
函數推入matchers
中;當遇到了關係操做符,會先將已經在matchers
中的所有篩選函數,用elementMatcher
函數包裹在一塊兒,再使用addCombinator
做爲紐帶返回一個函數,取代以前的matchers
。如此循環,直到將整個token所有遍歷結束。addCombinator
主要的功能就是根據關係操做符來查找兄弟元素和父級元素。app
我會把matcherFromTokens
,elementMatcher
,addCombinator
這三個函數都放在下面。框架
function matcherFromTokens(tokens) {
var checkContext, matcher, j,
len = tokens.length,
// 判斷是不是關係操做符開頭
leadingRelative = Expr.relative[ tokens[0].type ],
// 若是不是關係符開頭, 默認就是父祖集關係
implicitRelative = leadingRelative || Expr.relative[' '],
i = leadingRelative ? 1 : 0,
// 這裏就是baseMatcher
// addCombinator中做爲參數的fn 就是 filter
matchContext = addCombinator(function(elem) {
return elem === checkContext;
}, implicitRelative, true),
matchAnyContext = addCombinator(function(elem) {
return indexOf(checkContext, elem) > -1;
}, implicitRelative, true),
// 這個就是最後規則的合集, 它先把baseMatcher放到了合集裏面
// 通常狀況 (!leadingRelative && (xml || context !== outermostContext))會返回true 從而不去執行下面的函數
matchers = [ function(elem, context, xml) {
var ret = (!leadingRelative && (xml || context !== outermostContext)) || (
( checkContext = context ).nodeType ?
matchContext(elem, context, xml) :
matchAnyContext(elem, context, xml) );
checkContext = null;
return ret;
} ];
// 正向遍歷tokens
for (; i < len; i++) {
// 若是是關係符的話
if ((matcher = Expr.relative[tokens[i].type])) {
// 先將以有的規則用elementMatcher包裹在一塊兒, 再用addCombinator建立關聯;
// 生成的新的matcher代替原來所有的matcher
matchers = [addCombinator(elementMatcher(matchers), matcher)];
// 若是是 TAG ATTR PESUDO ID CLASS CHILD
} else {
matcher = Expr.filter[tokens[i].type].apply(null, toekns[i].matches);
// 若是是僞類, 這裏我嘗試了不少選擇器可是都沒有進入到這個if裏面
// 感受得是特別複雜的選擇器了
// 由於一直沒試出來, 因此就沒搞懂這裏究竟是幹啥的
if (matcher[expando]) {
j = ++i;
for (; j < len; j++) {
if (Expr.relative[tokens[j].type]) {
break;
}
}
return setMatcher(
i > 1 && elementMatcher(matchers),
i > 1 && toSelector(
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);
}
}
// 最後再用elementMatcher裹一層, 返回一個函數
return elementMatcher(matchers);
}
function addCombinator(matcher, combinator, base) {
var dir = combinator.dir,
skip = combinator.next,
key = skip || dir,
checkNonElements = base && key === 'parentNode',
doneName = done++;
// 若是是 > + 這兩個關係符
return combinator.first ?
// 檢查最近的父級或者兄弟元素
function(elem, context, xml) {
// 這個while循環elem是持續賦值的
// 這裏就是爲何說是紐帶的緣由了
// 在這裏循環以後, 找到的新元素放到以後的matcher裏面, 構成了經過seed一級一級向上查找的邏輯
while(elem = elem[dir]) {
// 當遇到元素節點的時候
if (elem.nodeType === 1 || checkNonElements) {
return matcher(elem, context, xml);
}
}
return false;
} :
// 檢查所有父級或者兄弟元素
function(elem, context, xml) {
var oldCahce, uniqueCache, outerCache,
newCache = [dirruns, doneName];
if (xml) {
while ((elem = elem[dir])) {
if (elem.nodeType === 1 || checkNonElments) {
if (matcher(elem, context, xml)) {
return true;
}
}
}
} else {
while ((elem = elem[dir])) {
// 這一塊都是緩存
// 緩存纔是最讓人看不懂的
// 這一塊,我也是沒看的特別懂, 若是有人理解這裏, 能夠告知一下
// 蟹蟹
if (elem.nodeType === 1 || checkNonElements) {
outerCache = elem[expando] || (elem[expando] = {});
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) {
return (newCache[2] = oldCache[2]);
} else {
// 這裏是不走緩存的 上面兩個if 應該都是從緩存中拿值
uniqueCache[key] = newCache;
if ( (newCache[2] = matcher(elem, context, xml)) ) {
return true
}
}
}
}
}
return false;
}
}
// 這個方法就是把一堆matacher 揉成一個
function elementMatcher(matchers) {
return matchers.length > 1 ?
function(elem, context, xml) {
var i = matchers.length;
// 注意這裏是i-- 說明這裏是倒敘的
// 這就是像剝洋蔥同樣, 一層一層判斷規則
while (i--) {
//只要有一個不知足, 直接返回false
if (!matchers[i](elem, context, xml)) {
return false;
}
return true;
}
} :
matchers[0];
}
複製代碼
在跑完了matcherFromTokens
,咱們再回過頭來繼續看compile
,當compile
的所有的matcherFromTokens
都跑完之後,就只剩返回作緩存和返回matcherFromGroupMatchers
了。matcherFromGroupMatchers
函數返回superMatcher
函數,superMatcher
函數使用來遍歷seed
,經過以前matcherFromTokes
運行得到的規則,對seed
進行篩選。
function matcherFromGroupsMatchers(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,
// 若是沒有seed 那麼就拿文檔所有的元素當作seed
elems = seed || byElement && Expr.find["TAG"]('*', outermost),
// 緩存用
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
len = elems.length;
if (outermost) {
// 這個outermostContext會在baseMatcher的時候用做判斷
outermostContext = context == document || context || outermost;
}
for (; i !== len && (elem = elems[i] != null); i++) {
if(byElement && elem) {
j = 0;
if (!context && elem.ownerDoucment != document) {
setDocumet(elem);
xml = !documentIsHtml;
}
// elementMatches會出現多個的狀況就是有逗號的狀況
// 這個時候只要知足一組規則就能夠把當前的元素推到結果集中
// 我們的例子只有一組規則
while ( (matcher = elementMatches[j++]) ) {
if (matcher(elem, context || document, xml)) {
results.push(elem);
break;
}
}
// 緩存
if (outermost) {
dirruns = dirrunsUnique;
}
}
// 沒有被匹配的那些元素
if (bySet) {
if ((elem = !matcher && elem)) {
matchedCount--;
}
if (seed) {
unmatched.push(elem);
}
}
}
matchedCount += i;
// 這裏的邏輯我並不太太懂, 由於我嘗試的例子中, 並無走到這裏的
// 這應該也是複雜選擇器纔會出現, 我試過:not(:not)的嵌套, 也沒走到這裏
// 但願有懂的人 能講解一下
// 這裏若是沒走for循環的話, 那麼i 是字符串'0' 而matchedCount是數字0
// 再包括matchedCount會-- 有可能即便走了for循環 也會致使會不相等
if (bySet && i !== matchedCount) {
j = 0;
while((matcher = setMatchers[j++])) {
matcher(unmatched, setMatched, context, xml);
}
if (seed) {
if (matchedCount > 0) {
while (i--) {
if ( !(unmatched[i] | setMatched[i]) ) {
setMatched[i] = pop.call(results);
}
}
}
setMatched = condense(setMatched);
}
push.apply(results, setMatched);
if (outermost && !seed && setMatched.length > 0 &&
(matchedCount + setMatchers.length) > -1) {
//排序
Sizzle.uniqueSort(results);
}
}
if (outermost) {
dirruns = dirrunsUnique;
outermoustContext = contextBackup;
}
// 這裏雖然return的是unmatched 可是results纔是最終的結果, 在select函數中最後return的是做爲參數的result
return unmatched;
}
return bySet ?
// marFunction 就是給參數的函數打expando標記的
markFunction(superMatcher) :
superMatcher;
}
複製代碼
看Sizzle
大概看了2個月, 在2020年以前把大概流程所有都看通了,算是過年了。在最後這段查找中,Sizzle用了大量的閉包,大量的柯里化函數,爲了就是保證所有的filter
函數入參,都爲elem, context, xml
。這是我看的第一個庫,看完了真的收穫不少,最開始由於看看司徒大大的書,一時興起想把Sizzle
看完,期間也以爲太難了想放棄,可是最後磕磕絆絆終因而看下來了。此次看完了等把JavaScript框架設計
都看完,再把jquery源碼擼了,再擼vue,而後再過年。哈哈哈哈哈。