7-4 Sizzle词法解析(上)
本节编程练习不计算学习进度,请电脑登录imooc.com操作

Sizzle词法解析(上)

什么是词法分析器(tokenize)?

词法分析器又称扫描器,词法分析是指将我们编写的文本代码流解析为一个一个的记号,分析得到的记号以供后续语法分析使用

'div > div.aaron input[name=ttt]'是一个相对复杂的选择器,这样的结构在不支持高级API的浏览器中是无法直接通过获取的,那么所有选择器的库就必须要干的一件事,把复杂选择器按照一样的设计规则,分解成浏览器原始API能够识别的结构,然后通过其他的方法找个这个结构。所以这里就要引入一个切割的算法了,也有点类似编译原理的词法分析。

所以引擎在遇到无法直接处理的复杂选择器时,就需要按照内部的规则进行分组了。

Sizzle的tokenize格式如下 :

{
   value:'匹配到的字符串', 
   type:'对应的Token类型',
   matches:'正则匹配到的一个结构'
}

tokenize需要解析的几种情况:

多重选择器,逗号分组

selector = 'div,input'

在出现逗号分隔符的时候,就说明选择所有指定的选择器的组合结果,所以需要分割成各自的处理模块,这种事情当然交给正则来做是最合适的

A: 常规的思路先是通过split(,)先把选择器劈成二部分,然后依次处理各自的模块

B:sizzle的思路则是循环一个一个分组出来的

我们假设一个复杂的选择器

div.aaron > input[name=ttt] , div p > span

这里涉及了3大块

1、分组逗号

2、层级关系

3、每种元素处理

具体我们参考下我写的代码区域

$("#test2").click(function() {
   //sizzle解析区域
}

其中分3大块的处理,其实很明了,因为这个案例的问题,我们没有层级关系的具体代码,下章会详细讲解,我们先理解这个思路。

sizzle对于分组过滤处理都用正则,其中都有一个特点,就是都是元字符^开头,限制匹配的初始,所以tokenize也是从左边开始一层一层的剥离。

其中会用到的正则:

//分组
var rcomma = /^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/;
//关系符
var rcombinators = /^[\x20\t\r\n\f]*([>+~]|[\x20\t\r\n\f])[\x20\t\r\n\f]*/;
//空白
var whitespace = "[\\x20\\t\\r\\n\\f]";

所以最终的结构:

groups: [
  tokens: {
    matches: ? type : ? value : ?
  },
  tokens: {
    matches: ? type : ? value : ?
  }
]

当然为什么是这样的结构,这是sizzle内部的一个解析规则罢了。后面就用这个规则去做匹配与筛选。

任务

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5. <script src="http://code.jquery.com/jquery-latest.js"></script>
  6. <title></title>
  7. </head>
  8. <body>
  9.  
  10. 多重选择器,逗号分隔处理
  11. <button id="test1">通过split分组</button>
  12. <button id="test2">sizzle的分组方式</button>
  13.  
  14. <div id="text">
  15. <p>
  16. <input type="text" />
  17. </p>
  18. <div class="aaron">
  19. <input type="checkbox" name="readme" />
  20. <input type="checkbox" name="ttt" />
  21. <input type="checkbox" name="aaa" />
  22. <p>慕课网</p>
  23. </div>
  24. </div>
  25.  
  26. <div>
  27. <input type="checkbox" name="readme" />
  28. <input type="checkbox" name="ttt" />
  29. <input type="checkbox" name="aaa" />
  30. <p>Aaron</p>
  31. </div>
  32.  
  33.  
  34. <script type="text/javascript">
  35.  
  36. //最简单的选择器
  37. var selector = 'div,input';
  38.  
  39. //常规分组,可以通过split
  40. $("#test1").click(function() {
  41. var soFar = selector.split(",");
  42. alert(soFar[0])
  43. alert(soFar[1])
  44. })
  45.  
  46.  
  47. //sizzle的分组方式
  48. $("#test2").click(function() {
  49.  
  50. //分组
  51. var rcomma = /^[\x20\t\r\n\f]*,[\x20\t\r\n\f]*/;
  52. //TAG
  53. var TAG = /^((?:\\.|[\w*-]|[^\x00-\xa0])+)/;
  54.  
  55. var soFar = selector;
  56. var match;
  57. var matched;
  58. var tokens;
  59. var groups = [];
  60.  
  61. while (soFar) {
  62.  
  63. //第一大块,分组关系
  64. //查找最左边的选择是不是逗号开头
  65. //matched用于处理第一进入
  66. //因为div input,div p 开始分解,第一个不是特殊符号
  67. if (!matched || (match = rcomma.exec(soFar))) {
  68. if (match) {
  69. // Don't consume trailing commas as valid
  70. soFar = soFar.slice(match[0].length) || soFar;
  71. }
  72. groups.push((tokens = []));
  73. }
  74.  
  75. //退出处理
  76. matched = false;
  77.  
  78. //第二大块,层级关系
  79. //
  80. // 这里用的最简单的处理关系,
  81. // 不涉及层级关系
  82. // ............................
  83. //
  84.  
  85. //第三大块,选择器
  86. match = TAG.exec(soFar)
  87. matched = match.shift();
  88. tokens.push({
  89. value : matched,
  90. type : 'TAG',
  91. matches : match
  92. });
  93. soFar = soFar.slice(matched.length);
  94.  
  95. if (!matched) {
  96. break;
  97. }
  98. }
  99.  
  100. alert(groups)
  101. })
  102.  
  103.  
  104.  
  105.  
  106. </script>
  107.  
  108. </body>
  109. </html>
下一节