Sizzle揭秘—Sizzle选择器引擎的入口

时间:2022-12-18 17:20:25

前面说了Sizzle的两大利器,今天核心Sizzle结构

显示行数的有点问题,因为代码太长是分了两次,后来拼起来的见谅!

   
   1: //Sizzle选择器的入口

2: var Sizzle = function(selector, context, results, seed) {

3: //初始化各个数据结果集和初始上下文

4: results = results || [];

5: var origContext = context = context || document;

6: //如果初始的上下文即不是元素节点或是document节点则返回空的结果集

7: if ( context.nodeType !== 1 && context.nodeType !== 9 ) {

8: return [];

9: }

10: //如果选择字符串为空或selector不是string类型则直接返回结果集result

11: if ( !selector || typeof selector !== "string" ) {

12: return results;

13: }

14:

15: var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context),

16: soFar = selector;

17: //chunker是Sizzle中一个重要的正则表达式 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,

18: //其实蛮长,引用一下别人的解释

19: //第一部分:((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|

20: [^[\]]+)+\]|\\.|[^ >+~, (\[]+)+|[>+~]]

21: 第二部分: (\s*,\s*)?

22: 第一部分可以以|(表示或的意思)为分界符做进一步的切分。则切分后的正则表达式如下所示

23: // //正则表达式起始符

24: // ( //第一个子表达式

25: // (?:\((?:\([^()]+\) //匹配伪类选择符

26: // |[^()]+)+\) //匹配函数式选择符

27: // |\[(?:\[[^[\]]*\] //匹配属性选择符

28: // |['"][^'"]*['"] //匹配属性选择符

29: // |[^[\]'"]+)+\] //匹配属性选择符

30: // |\\. //匹配Class选择符

31: // |[^ >+~,(\[\\]+)+ //匹配关系选择符

32: // |[>+~] //匹配关系选择符

33: // )

34: // (\s*,\s*) //匹配选择符组中间的分隔符

35: // ? //非贪婪模式匹配

36: // / //正则表达式终止符

37: // g //正则表达式属性,全局匹配

38: // /*

39: //解释了这么多,就是将整个选择字符串按“+” “ ” “>” "~"分割,遇到","就停止匹配

40: //举个完整的例子就是"div ~ ul li[title='a'].sel , div ul li#mm" 这个选择字符串进过这次正则表达式处理后就会有parts=["div","~","ul"," ","li[title='a'].sel"] extra="div ul li#mm";

41: // Reset the position of the chunker regexp (start from head)

42: while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {

43: soFar = m[3];

44:

45: parts.push( m[1] );

46: //如果m[2]不为空则说明匹配了','并列连接符,则将‘,’后面的内容作为另外一次sizzle选择的选择字符串,后面会看到

47: if ( m[2] ) {

48: extra = m[3];

24: break;

25: }

26: }

//Sizzle对有伪位置选择符的选择字符串和没有处理是不一样的 origPos=/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/

27: //Sizzle对有伪位置类选择器的选择字符串的处理和没有是不一样的。有的话会按照从左到右的方向进行解析,没有则按照Sizzle的标准解析从右往左进行解析

28: if ( parts.length > 1 && origPOS.exec( selector ) ) {

29: //如果选择字符串按照层级关系只有两部分,则合并这两项 举例 parts=[">","li.sel"] 会合并为"> li.sel".

30: //relative同Expr.filter Expr.prefilters Expr.find 是作用其实都相仿。relative对象有"+" ">" "" "~"这四个属性,对不同的层级关系就行处理

31: if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {

32: set = posProcess( parts[0] + parts[1], context );

33: } else {

34: //如果第一个字符串是一个层级标识符,则已则以[context]作为结果集初始值 否则弹出第一个元素就行解析并将解析结果作为结果集初始值,如"ul > li.sel"则 调用Sizzle("ul",context),,所有ul DOM作为结果集初始值

35: //其实这是一个递归调用,先不用关注具体的递归细节,我们先将所有的流程走下来

36: set = Expr.relative[ parts[0] ] ?

37: [ context ] :

38: Sizzle( parts.shift(), context );

39: //对parts中的元素就行遍历

40: while ( parts.length ) {

41: //对剩余字符串的解析基本上就是组合层级字符串和后面紧接的部分 举例来说假如经过上面的结果集初始化后 选择字符串还剩下parts=[">","div.mm","~","ul","li#gg"]

42: //解析循环调用过程下 : set=posProcess(">div.mm",set);

43: // set=posProcess("~ul",set)

44: // set=posProcess("li#gg");

45: // selector = parts.shift();

46:

47: if ( Expr.relative[ selector ] ) {

48: selector += parts.shift();

49: }

50:

51:

52:

53: //在posProcess是先将selector的伪位置类选择符剔除,然后递归调用Sizzle(“剔除后的字符换”,set),然后对Sizzle返回的结果进行伪位置类选择符过滤
//Sizzle("被剔除的伪位置选择符",上一步Sizzle返回的结果),这就完成了一个字符串的处理了
 54: set = posProcess( selector, set );

55: }

56:

57: }

58: //这部分不是jQuery的源码 是我在关于那篇remove参数中出现伪位置类选择符时出现非预期结果时,加上的测试代码

59: //如果有兴趣可以在我的博客中找到那篇文章,如果没有兴趣直接跳过,没有任何影响。

60: // if(parts.length==0&&seed){

61: // var p_result=new Array();

62: // var seeds=makeArray(seed);

63: // var i;

64: // for(i=0;i<seeds.length;i++)

65: // {

66: // var j;

67: // for(j=0;j<set.length;j++){

68: // if(seeds[i]==set[j])

69: // p_result.push(seeds[i]);

70: // }

71: // }

72: // set=p_result;

73: //

74: //增加的代码至此结束 }

75: } else {

76: //如果没有伪位置类选择符则是另外一种处理方法

77: // Take a shortcut and set the context if the root selector is an ID

78: // (but not if it'll be faster if the inner selector is an ID)

79: //上面有英文的注释,基本上就是做了一个小的优化,但这种优化仅在第一个分割字符串含有ID而最后一个分割字符串没有ID的情况下进行 这对性能的提高很有好处

80: //因为ID是唯一的,如果最后一个分割字符串是具有ID则很容找出来,而在这种情况下就不会去做这种优化了

81: if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&

82: Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {

83: var ret = Sizzle.find( parts.shift(), context, contextXML );

84: //这里find函数和filter函数在前面是介绍过的find函数返回的ret中的expr是剩余字符串 如"ul.sel"则返回的结果是ret中set是多有class=sel的元素,expr中是ul,便于下一步进行过滤

85: //filter过滤函数不像find函数值解析一部分,而是解析全部。如ul[title='a'].sel就会在一次过滤中全部处理完

86: context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];

87: }

88:

89: if ( context ) {

90: //如果seed种子集不为空,则让他作为初始结果集(可以对比一下有无伪位置类选择符处理可以发现有伪位置类选择符的处理是没有用到seed的)

91: //如果不是则弹出最后的一个字符串调用find函数

92: //最后对字符串的剩余部分进行过滤

93: var ret = seed ?

94: { expr: parts.pop(), set: makeArray(seed) } :

95: Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );

96: set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;

97: //如果此时parts中还有元素则将结果集转化为数组,如果没有元素就射prune=false ,如果原始的选择字符串没有层级关系就会出现这种情况,"li#gg"就是例子

98: if ( parts.length > 0 ) {

99: checkSet = makeArray(set);

100: } else {

101: prune = false;

102: }

103: //在前面的checkset的基础上进一步筛选元素

104: while ( parts.length ) {

105: var cur = parts.pop(), pop = cur;

106:

107: if ( !Expr.relative[ cur ] ) {

108: cur = "";

109: } else {

110: pop = parts.pop();

111: }

112: //此时pop代表层次过滤函数的参数,cur代表层级过滤类型(“+” “>" "" "~")

113:

114: if ( pop == null ) {

115: pop = context;

116: }

117: //调用对应的层级筛选函数 其实可能已经知道了relative内部也是通过调用Sizzle.filter进行过滤的

118: Expr.relative[ cur ]( checkSet, pop, contextXML );

119: }

120: } else {

121: checkSet = parts = [];

122: }

123: }

124: //如果是没有层级关系的选择字符串会出现这样的情况 "li#gg"为例,参考上面关于此种情况的解释

125: if ( !checkSet ) {

126: checkSet = set;

127: }

128: //如果到这不仍然为空则选择字符串的编写是有问题的

129: if ( !checkSet ) {

130: Sizzle.error( cur || selector );

131: }

132: //call就是以另一种方式调用一个函数

133: if ( toString.call(checkSet) === "[object Array]" ) {

134: //prune是在上面判断的,如在解析没有层级关系的字符串时就会赋值为false,此时直接将结果赋值到result后面。

135: if ( !prune ) {

136: results.push.apply( results, checkSet );

137: } else if ( context && context.nodeType === 1 ) {

138: //我们知道在filter函数中inplace设为true是会修改CheckSet的,符合过滤条件的元素设为true,不符合设为false。那下面应该是很容易理解的了。set是最开始的结果集。

139: for ( var i = 0; checkSet[i] != null; i++ ) {

140: if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {

141: results.push( set[i] );

142: }

143: }

144: } else {

145: //是其他情况则将Checkset中节点元素附加到结果集后面,

146: for ( var i = 0; checkSet[i] != null; i++ ) {

147: if ( checkSet[i] && checkSet[i].nodeType === 1 ) {

148: results.push( set[i] );

149: }

150: }

151: }

152: } else {

153: makeArray( checkSet, results );

154: }

155: //如果有并联连接字符串则递归处理后面由“,”分开的字符串

156: if ( extra ) {

157: Sizzle( extra, origContext, results, seed );

158: Sizzle.uniqueSort( results );

159: }

160: //返回结果集

161: return results;

162: };