简介
选中得到元素之后,我们要进行验证得到元素是不是被正确选择了,比如“.header > .nav .nav-item”,选中".nav-item"class属性的元素。我们首先要确定上下文,比如:文档根节点document这个文档中查找这个元素,如果把上下文缩小的话就是在.header作为上下文找,如果找到了.nav-item元素,然后一直验证.nav-item的祖先元素是否有一个.nav 的class属性,继续向祖先元素查找 .header 这个class属性。如果都正确的话则说明这个元素是正确选择的。
上节中已经介绍了如何选根据选择器,选中元素的,这节非常重要是来介绍是如何验证这个元素是正确的。这里把选中的元素验证上下文与过滤称编译。首先来介绍整个css选择器引擎的大概执行过程,请看下图。
编译过程
addCombinator
这个函数顾名思义,是帮助添加组合器的函数,css的组合选择器4种:
- (space)匹配指定元素后的所有元素。example
- (>)选中指定元素后的所有子元素。example
- (+)必须拥有相同的父元素,选择紧跟在指定元素后的元素。example
- (~)一般同级选择器选择指定元素的后面指定的所有兄弟元素。example 这四种根据紧密关系不同又添加了属性first来进行区别,可以查看以上demo体会。
/**
matcher 是一个function被传入
combinator是一个对象 形式如:{
dir: "parentNode",
first: true
},
base true | false
**/
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){...} :
function (elem, context, xml){...} {
var oldCache, uniqueCache, outerCache, newCache = [dirruns, doneName]
}
}
很明显返回的是一个闭包函数,等到需要的时候传入elem,context, xml 这三个参数就可以了。这里简单分析一下这个两个不同函数的作用。
最后执行哪个函数的觉定是又combinator这个方法来决定的而这个方法的参数形式一共有3种形式,第一种是开头的{dir: "",next: ""} 第二种{dir: "",first: true} 第三种{dir:""}。 dir对应选择器分别有“ ”、“>”、 "+"、“~”, >、+ 分别是指子代选择器,+ 兄弟选择器。选择的元素比较关系贴近,就是后代之中,或者紧邻的兄弟元素。
检查最近的祖先元素或者前导元素,如果是first:true
// Check against closest ancestor/preceding element,如果找到了最近的祖先元素就返回true,没找到返回false
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
//不能给任意的xml节点设置数据
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)
// 给小于ie9的浏览器做一个属性防止克隆
uniqueCache = outerCache[elem.uniqueID] ||
(outerCache[elem.uniqueID] = {});
// 如果是fildList标签
if (skip && skip === elem.nodeName.toLowerCase()) {
elem = elem[dir] || elem;
//查看是不是ie浏览器
} else if ((oldCache = uniqueCache[key]) &&
oldCache[0] === dirruns && oldCache[1] === doneName) {
// Assign to newCache so results back-propagate to previous elements
// 把旧的oldCache[2]赋值给新的oldCache[2]
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
根据传入的元素与匹配器来匹配元素与匹配器是否一致
// 传入的matches的第一个参数是匹配上下文
// function (elem, context, true) {indexOf(checkContext, elem) > -1}
// function (elem, context, xml) {return checkContext === elem}
// 如果这个元素不在这个上下文中,就是匹配失败
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
}:
machers[0]
}
matcherFromTokens
matcherFromTokens是非常重要的方法,拿到解析到词法解析的结果,对每个选择器做一个匹配,也就是验证,他连接了元素与选择器之间的关系。
// 拿到解析到词法解析的结果,对每个选择器做一个匹配,也就是验证
function matchFromTokens(tokens){
//检查上下文,
var checkContext,matcher, j,
//拿到剩余要验证的选择器的个数
len = tokens.lengthm
// 查看第一个元素需要上下文,或者前导元素吗
leadingRelative = Expr.relative[tokens[0].type],
// 如果需要前导上下问1,隐含的不需要,
// 如果不是需要,则添加一个隐式的前导元素
impliciRelative = leadingRelative || Expr.relative[" "],
// 前导有就是1没有0
// 前导元素不存在 i是0,存在前导元素i是1
i = leadingRelative ? 1 : 0,
//匹配上下文,返回true或者false。传入一个元素就可以
// 根据是否需要添加前导元素,来匹配上下文
matchContext = addConbinator(function (elem) {
return elem === checkContext
}, implicitRelative, true),
// 匹配任何多的上下文,根据传入的前导元素,如果有前导元素,拿到前导元素,没有的话添加一个后代选择器
matchAnyContext = addCombinator(function (elem) {
return indexOf(checkContext, elem) > -1
}, implicitRelative, true),
// matchers 数组中存放着匹配器,到时候根据,有没有前导元素,
// xml或者 context !==outermostContext
// 或者把当前上下文赋值给checkContext查看nodeType是否存在,如果存在的话就匹配一个精准的上下文,如果不存在的话就匹配任意的一个上下文,
matchers = [function (elem, context, xml) {
var ret =
(!leadingRelative && (xml || context !==outermostContext))
|| (checkContext = context).nodeType ?
matchContext(elem, context, xml):
matchAnyContext(elem, context, xml)
// 把chekContext给置空
var checkContxt = null
// 返回当前的上下文
return ret
}]
for (; i < len; i++) {
// 从第一个匹配器开始计算
if ((matcher = Expr.relative[tokens[i].type])) {
matchers = [addCombinator(elementMatcher(matchers), matcher)]
} else {
// 过滤这个选择器,并传入当前匹配的matches ['box']
matcher = Expr.filter([tokens[i].type]).apply(null, tokens[i].matches)
// 如果当前machter有这个属性
if (matcher[expando]) {
// 就从下一个元素开始继续处理
j = ++ i
for (; j < len; j++) {
// 如果是相对选择器,则跳出本次循环,进行下次处理
if (Exp.relative[tokens[j].type]) {
break
}
}
return setMatcher(
i > 1 && elementMatcher(matchers),
i> 1 && toSelector(
// tokensi -2 的type是倒数第二个元素,tokens如果是“ ”则给* 否则空
// 连接上去后删除i-1长度,转成selector
tokens.slice(0, i -1).concat({value: tokens[i -2].type === " " ? "*": ""})
).replace(rtrim, "$1"),
matcher,
i < j&& matcherFromTokens(tokens.slice(i,j )),
j < len && matcherFronTokens((tokens = tokens.slice(j)))
j < len && toSelector(tokens)
)
}
matchers.push(matcher)
}
}
return elementMatcher(matchers)
}
compile
需要校验的选择器,selector词法解析的结果。
compile = Sizzle.compile = function(selector, match /* Internal Use Only */ ) {
var i,
// 设置匹配器
setMatchers = [],
// 元素匹配器
elementMatchers = [],
// 是否有编译换成
cached = compilerCache[selector + " "];
// 如果没有编译换成
if (!cached) {
//如果不存在match,词法解析的结果,重新新进行词法解析
// Generate a function of recursive functions that can be used to check each element
if (!match) {
match = tokenize(selector);
}
//查看是否有分组选择器
i = match.length;
//存最后一组开始
while (i--) {
// 对匹配来自Tokens的选择器,传入一组选择器,返回对这个匹配选择器的缓存
//compilerCache(selector) = 做一个缓存
//matcherFromTokens这里就相当于是一个闭包,存储了对每一个选择器的验证顺序,在使用的时候再调用。
cached = matcherFromTokens(match[i]);
// 如果这个缓存已经设置过了,则放入elementMatchers
if (cached[expando]) {
setMatchers.push(cached);
// 没做过缓存则放入elementMatchers中,进行最后的校验
} else {
elementMatchers.push(cached);
}
}
// 缓存这个编译的结果
// Cache the compiled function
cached = compilerCache(
selector,
matcherFromGroupMatchers(elementMatchers, setMatchers)
);
// Save selector and tokenization
cached.selector = selector;
}
// 返回编译结果
return cached;
};
matcherFromGroupMatchers
/**
elementMatchers是通过元素来匹配
setMatchers是通过已经通过缓存的数据来进行编译
**/
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 = [],
// 最外层的context
contextBackup = outermostContext,
// 有缓存的元素或者直接查找所以元素
elems = seed || byElement && Expr.find["TAG"]("*", outermost)
// 独一无二的dir走了几次
dirrunsUnique = (dirruns += contextBackup == null ? 1: Math.random() || 0.1)
if (outermost) {
// support IE11+
outermostContext = context == document || context || outermost
}
// 传递元素给elementMathers 直接添加到结果中
for( ; i < len && (elem = elems[i]) != null; i++) {
if (byElement && elem) {
j = 0;
//上下文不存在同时ownerDocument != document
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
}
}
// 追踪设置不匹配的元素
if (bySet) {
// 遍历所有可能的匹配器
if ((elem = !matcher && elem)) {
matchedCount --
}
if (seed) {
unmatched.push(elem)
}
}
}
// i是元素访问计数器
matchedCount += i
// 不匹配的元素设置过滤器
// 如果没有不匹配的元素(matchedCount 与i相等)除非我们没有访问上述循环中的任何元素,因为没有元素匹配器和种子
// 低等初始字符串“0” 允许i去仅在这种情况下保留一个字符串,这样将导致结果是“00”matchCount,与i不同,但是也是数字0
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)
}
/add matches to results 添加匹配到的到results
push.apply(results, setMatched)
//种子元素不存在设置匹配成功匹配器规定排序
if (outermost && !seed && setMathed.length > 0 &&
(matchedCount + setMatchers.length) > 1
) {
Sizzle.uniqueSort(results)
}
return unmatched
}
}
return bySet ? markFunction(SuperMatcher): superMather;
}