3.3 设计思路
在正式开始分析Sizzle的源码实现之前,先来讨论和分析下如果要执行一段选择器表达式,或者说设计一个简化版的选择器引擎,需要做些什么工作。下面以"div.red>p"为例来模拟执行过程,具体来说有从左向右查找和从右向左查找两种思路:
1)从左向右:先查找"div.red"匹配的元素集合,然后查找匹配"p"的子元素集合。
2)从右向左:先查找"p"匹配的元素集合,然后检查其中每个元素的父元素是否匹配"div.red"。
无论是从左向右还是从右向左,都必须经历下面3个步骤:
1)首先要能正确地解析出"div.red>p"中的"div.red"、"p"和">",即解析出选择器表达式中的块表达式和块间关系符。这一步是必需的,否则根本无从下手。
2)然后要能正确地找到与"div.red"或"p"匹配的元素集合,即查找单个块表达式的匹配元素集合。以"div.red"为例,可以有两种实现方式:
a.?先查找匹配"div"的元素集合,然后从中过滤出匹配".red"的元素集合。
b.?先查找匹配".red"的元素集合,然后从中过滤出匹配"div"的元素集合。
不管采用以上哪种方式,这个过程都可以分解为两个步骤:第一步用块表达式的一部分进行查找,第二步用块表达式的剩余部分对查找的结果进行过滤。
3)最后来处理"div.red"和"p"之间的关系符">",即处理块表达式之间的父子关系。在这一步骤中,从左向右和从右向左的处理方式是截然不同的:
a.?从左向右:找到"div.red"匹配的元素集合的子元素集合,然后从中过滤出匹配"p"的子元素集合。
b.?从右向左:检查每个匹配"p"的元素的父元素是否匹配"div.red",只保留匹配的元素。
无论采用以上哪种方式,这个过程都可以分解为两个步骤:第一步按照块间关系符查找元素,第二步用块表达式对查找的结果进行过滤。不论元素之间是哪种关系(父子关系、祖先后代关系、相邻的兄弟关系或不相邻的兄弟关系),都可以采用这种方式来查找和过滤。
另外,如果还有更多的块表达式,则重复执行第3步。
对于前面的3个步骤,可以进一步提炼总结,如下:
1)处理选择器表达式:解析选择器表达式中的块表达式和块间关系符。
2)处理块表达式:用块表达式的一部分查找,用剩余部分对查找结果进行过滤。
3)处理块间关系符:按照块间关系符查找,用块表达式对查找结果进行过滤。
从前面对选择器表达式的执行过程的分析,还可以推导分析出以下结论:
从左向右的总体思路是不断缩小上下文,即不断缩小查找范围。
从右向左的总体思路是先查找后过滤。
在从左向右的查找过程中,每次处理块间关系符时都需要处理未知数量的子元素或后代元素,而在从右向左的查找过程中,处理块间关系符时只需要处理单个父元素或有限数量的祖先元素。因此,在大多数情况下,采用从右向左的查找方式其效果要高于从左向右。
在了解了两种执行思路后,现在再来看看Sizzle,它是一款从右向左查找的选择器引擎,提供了与前面3个步骤相对应的核心接口:
正则chunker负责从选择器表达式中提取块表达式和块间关系符。
方法Sizzle.find( expr, context, isXML )负责查找块表达式匹配的元素集合,方法Sizzle.filter( expr, set, inplace, not )负责用块表达式过滤元素集合。
对象Sizzle.selector.relative中的块间关系过滤函数根据块间关系符过滤元素集合。
函数Sizzle( selector, context, results, seed )则按照前面3个步骤将这些核心接口组织起来。
本节对选择器引擎和Sizzle的设计思路作了探索和概述,接下来看看Sizzle的源码实现。