sizzle(六) 验证过滤得到正确的元素节点

516 阅读5分钟

简介

  选中得到元素之后,我们要进行验证得到元素是不是被正确选择了,比如“.header > .nav .nav-item”,选中".nav-item"class属性的元素。我们首先要确定上下文,比如:文档根节点document这个文档中查找这个元素,如果把上下文缩小的话就是在.header作为上下文找,如果找到了.nav-item元素,然后一直验证.nav-item的祖先元素是否有一个.nav 的class属性,继续向祖先元素查找 .header 这个class属性。如果都正确的话则说明这个元素是正确选择的。

  上节中已经介绍了如何选根据选择器,选中元素的,这节非常重要是来介绍是如何验证这个元素是正确的。这里把选中的元素验证上下文与过滤称编译。首先来介绍整个css选择器引擎的大概执行过程,请看下图。

编译过程

image.png

addCombinator

  这个函数顾名思义,是帮助添加组合器的函数,css的组合选择器4种:

  1. (space)匹配指定元素后的所有元素。example
  2. (>)选中指定元素后的所有子元素。example
  3. (+)必须拥有相同的父元素,选择紧跟在指定元素后的元素。example
  4. (~)一般同级选择器选择指定元素的后面指定的所有兄弟元素。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;
}

Sizzle源码分析(一) 基本概念

Sizzle源码分析(二) 工具方法

Sizzle源码分析(三) 兼容处理

Sizzle源码分析(四) sizzle静态方法分析

Sizzle源码分析(五) 如何根据css选择器,选中对应的html节点

Sizzle源码分析(六) 验证过滤得到正确的元素节点