首页 > 语言 > JavaScript > 正文

jQuery选择器源码解读(三):tokenize方法

2024-05-06 16:17:54
字体:大 中 小
来源:转载
供稿:网友

这篇文章主要介绍了jQuery选择器源码解读(三):tokenize方法,本文用详细的注释解读了tokenize方法的实现源码,需要的朋友可以参考下

 

 
  1. /* 
  2. * tokenize方法是选择器解析的核心函数,它将选择器转换成两级数组groups 
  3. * 举例: 
  4. * 若选择器为“div.class,span”,则解析后的结果为: 
  5. * group[0][0] = {type:'TAG',value:'div',matches:match} 
  6. * group[0][1] = {type:'CLASS',value:'.class',matches:match} 
  7. * group[1][0] = {type:'TAG',value:'span',matches:match} 
  8. * 由上述结果可以看出,groups的每一个元素以逗号分隔的选择器块的解析结果, 
  9. * 另外,上述结果中的matches等于模式匹配的结果,由于在此不方便写清楚, 
  10. * 故只把代码matches:match写在这里。 
  11. *  
  12. * tokenize方法完成如下两个主要任务: 
  13. * 1、解析选择器 
  14. * 2、将解析结果存入缓存中,以备后用 
  15. *  
  16. *  
  17. * @param selector 待解析的选择器字符串 
  18. * @param parseOnly 为true时,说明本次调用是匹配子选择器 
  19. * 举个例子:若初始选择器为"div:not(.class:not(:eq(4))):eq(3)" 
  20. * 代码首先匹配出TAG选择器div, 
  21. * 之后匹配出的pseudo选择器字符串是:not(.class:not(:eq(4))):eq(3), 
  22. * 代码会把“.class:not(:eq(4))):eq(3”作为not的括号内的值进一步进行解析, 
  23. * 此时代码在调用tokenize解析时,parseOnly参数会传入true. 
  24. */ 
  25. function tokenize(selector, parseOnly) { 
  26. var matched, match, tokens, type, soFar, groups, preFilters,  
  27. // 获取缓存中的结果 
  28. cached = tokenCache[selector + " "]; 
  29.  
  30. /* 
  31. * 若缓存中有selector对应的解析结果 
  32. * 则执行if中语句体 
  33. */ 
  34. if (cached) { 
  35. // 若是对初始选择器解析(parseOnly!=true),则返回缓存结果, 
  36. // 若不是,则返回0 
  37. return parseOnly ? 0 : cached.slice(0); 
  38. } 
  39.  
  40. /* 
  41. * 由于字符串在javascript中不是作为对象来处理的, 
  42. * 所以通过赋值,代码就自动复制了一个新字符串给了soFar, 
  43. * 这样,对soFar的任何处理都不会影响selector的原有数据 
  44. */ 
  45. soFar = selector; 
  46. groups = []; 
  47. // 此处赋值,仅仅用于减少后续代码字数,缩短执行路径 
  48. preFilters = Expr.preFilter; 
  49.  
  50. while (soFar) { 
  51.  
  52. // Comma and first run 
  53. /* 
  54. * rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*") 
  55. * rcomma用来判定是否存在多个选择器块,即用逗号隔开的多个并列的选择器 
  56. *  
  57. * 下面条件判定依次为: 
  58. * !matched:若是第一次执行循环体,则为true;否则为false。 
  59. * 这里matched即作为是否第一次执行循环体的标识, 
  60. * 也作为本次循环中soFar是否以非法字符串(即非合法单一选择器)开头的标志。 
  61. * (match = rcomma.exec(soFar):获取符合rcomma的匹配项 
  62. */ 
  63. if (!matched || (match = rcomma.exec(soFar))) { 
  64. if (match) { 
  65. // Don't consume trailing commas as valid 
  66. /* 
  67. * 剔除掉第一个逗号及之前的所有字符 
  68. * 举个例子: 
  69. * 若初始选择器为:"div.news,span.closed", 
  70. * 在解析过程中,首先由后续代码解析完毕div.news,剩下",span.closed" 
  71. * 在循环体内执行到这里时,将逗号及之前之后连续的空白(match[0])删除掉, 
  72. * 使soFar变成"span.closed",继续执行解析过程 
  73. *  
  74. * 在这里,若初始选择器的最后一个非空白字符是逗号, 
  75. * 那么执行下面代码时soFar不变,即soFar.slice(match[0].length)返回空字符串, 
  76. * 故最终返回的是||后面的soFar 
  77. */ 
  78. soFar = soFar.slice(match[0].length) || soFar; 
  79. } 
  80.  
  81. /* 
  82. * 在第一次执行循环体或者遇到逗号分割符时,将tokens赋值为一个空数组, 
  83. * 同时压入groups数组 
  84. */ 
  85. groups.push(tokens = []); 
  86. } 
  87.  
  88. matched = false; 
  89.  
  90. // Combinators 
  91. /* 
  92. * rcombinators = new RegExp( 
  93. * "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"), 
  94. * rcombinators用来匹配四种关系符,即>+~和空白 
  95. *  
  96. * 若soFar中是以关系符开始的,则执行if内的语句体 
  97. */ 
  98. if ((match = rcombinators.exec(soFar))) { 
  99. /* 
  100. * 将match[0]移除match数组,同时将它赋予matched 
  101. * 若原本关系符两边带有空格,则此时match[0]与matched是不相等的 
  102. * 举个例子: 
  103. * 若soFar = " + .div"; 
  104. * 执行match = rcombinators.exec(soFar)后, 
  105. * match[0] = " + ",而match[1]="+"; 
  106. * 执行完matched = match.shift()后, 
  107. * matched=" + ",而match[0]="+"; 
  108. */ 
  109. matched = match.shift(); 
  110. // 将匹配结果压入tokens数组中 
  111. tokens.push({ 
  112. value : matched, 
  113. // Cast descendant combinators to space 
  114. /* 
  115. * rtrim = new RegExp("^" + whitespace + "+|((?:^|[^////])(?:////.)*)" 
  116. * + whitespace + "+$", "g"), 
  117. * whitespace = "[//x20//t//r//n//f]"; 
  118. *  
  119. * 下面match[0].replace(rtrim, " ")的作用是将match[0]左右两边的空白替换为空格 
  120. * 但是由于其上的match.shift的作用,match[0]已经是两边不带空白的字符串了, 
  121. * 故此出的替换是没有用途的代码 
  122. */ 
  123. type : match[0].replace(rtrim, " ") 
  124. }); 
  125.  
  126. // 将关系符之后的字符串赋予soFar,继续解析 
  127. soFar = soFar.slice(matched.length); 
  128. } 
  129.  
  130. // Filters 
  131. /* 
  132. * 下面通过for语句对soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO类型的选择器 
  133. * 若匹配到了,则先调用该类型选择器对应的预过滤函数, 
  134. * 然后,将结果压入tokens数组,继续本次循环。 
  135. */ 
  136. for (type in Expr.filter) { 
  137. /* 
  138. * match = matchExpr[type].exec(soFar):对soFar调用type类型的正则表达式对soFar进行匹配, 
  139. * 并将匹配结果赋予match。若未匹配到数据,则match为undefined。 
  140. * !preFilters[type]:若不存在type类型的预过滤函数,则为true 
  141. * match = preFilters[type](match):执行预过滤,并将结果返回给match 
  142. *  
  143. */ 
  144. if ((match = matchExpr[type].exec(soFar)) 
  145. && (!preFilters[type] || (match = preFilters[type] 
  146. (match)))) { 
  147. // 将match[0]移除match数组,同时将它赋予matched 
  148. matched = match.shift(); 
  149. // 将匹配结果压入tokens数组中 
  150. tokens.push({ 
  151. value : matched, 
  152. type : type, 
  153. matches : match 
  154. }); 
  155. // 将匹配结果之后的字符串赋予soFar,继续解析 
  156. soFar = soFar.slice(matched.length); 
  157. } 
  158. } 
  159.  
  160. /* 
  161. * 若matched==false, 
  162. * 则说明本次循环没有有效的选择器(包括关系符和id、class等类型选择器) 
  163. * 因此,解析到当前位置遗留下来的soFar是非法的选择器字符串 
  164. * 跳出while循环体 
  165. */ 
  166. if (!matched) { 
  167. break; 
  168. } 
  169. } 
  170.  
  171. // Return the length of the invalid excess 
  172. // if we're just parsing 
  173. // Otherwise, throw an error or return tokens 
  174. /* 
  175. * 若不是对初始选择器字符串进行解析(!parseOnly==true), 
  176. * 则返回soFar.length,此时的soFar.length代表连续有效的选择器最终位置, 
  177. * 后续文章将以实例进行说明 
  178. * 若是对初始选择器字符串进行解析,则看soFar是否还有字符, 
  179. * 若是,则执行Sizzle.error(selector)抛出异常; 
  180. * 若不是,则执行tokenCache(selector, groups).slice(0)将结果压入缓存,并返回结果的副本。 
  181. */ 
  182. return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) : 
  183. // Cache the tokens 
  184. tokenCache(selector, groups).slice(0); 
  185. } 

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选