作者:Ruby's Louvre
原文:http://www.cnblogs.com/rubylouvre/archive/2009/11/22/1608053.html
本来准备开讲jQuery源码学习笔记六的,但心中有佛才能看到佛,有些人连选择器都不知什么东西,直接讲下去,估计有人会看得云里雾里,满头雾水了。加之,John Resig有一种把代码写得不知所云的魔力,他拥有强大的驭驾代码的实力,他自己看当然没问题,其他人则要命了。常常是一句代码调用几个方法,每个方法相隔几十行甚至上百行,而且这些方法还常常是幌子,真正做事是其他代码。这样盘根错节的代码与Base2有得一拼。最后,选择器本身也是非常复杂的东西,有必须独立提出来说一下。
选择器其实早已实现,不过都是一些不起眼但超常用的方法:getElementById,getElementsByName,getElementsByTagName。但外国人不满意这些API,于是搞出了getElementsByClassName,进而是getElementsBySelector,支持当时已公布的所有CSS2.1选择符!然后Prototype用一个$与一个$$把它们统括起来。之所以这样,其动机显然易见,如果返回一个就不用索引号去取了!像jQuery那样无论是返回多少个,反正都包裹在jQuery对象,都要用索引号或get方法把真正DOM元素取出来,因此用不用$$都差不多,于是$统一天下!
但这个包打天下的选择器其实包括多少东西呢?细算一下,ID选择器,标签选择器,联合选择器,属性选择器,关系选择器与伪类选择器。ID选择器,就是以#开头的,标签选择器就是tagName,联合选择器就是那个逗号,属性选择器就是用中括号括起来的字段,里面也非常复杂,可以单纯只有属性名,不用属性值,如果有属性值,又分好几种情况匹配(详见我的另一篇博文《getElementsByAttribute》)。像getElementsByName应该归并于属性选择器,因为name也是一个属性。关系选择符又细分为四个:兄长(~),亲子(>),相邻(+)与后代(空格)。伪类选择器的成员非常庞大,因为CSS3新添加的这些伪类选择符也是如此。除了伪元素选择符与页面伪类与链接伪类等少量无法转换为选择器外,其他基本都能。这些符合资格的伪类有目标伪类,结构伪类,语言伪类与UI状态伪类。语言伪类与UI状态伪类与属性选择符很相近,因此实现手段也一致。目标伪类我们可以通过截取地址栏上的参数,用IE选择器搞定。结构伪类则又是个大家族,分为两派:root与其他。:root就是根节点documentElement。其他都可以统称这子元素过滤器,我们可以根据顺序(nth-child)来筛选,可以根据类型来选(only-of-type),又可以组合类型与顺序来选,还可以根据元素里面有没有内容来选(empty),里面还有一些变种,如那些带first与last的。在jQuery,为了照顾那些美工MM,John Resig又定义许多快捷的选择符,这个,自己看文档吧。
兄长选择符
提示:可修改后代码再运行!
相邻选择符
提示:可修改后代码再运行!
亲子选择符
提示:可修改后代码再运行!
好了,我们说一下它的实现原理吧。通常都是选择一个字符串,最后返回一组元素。如果字符串是“#aaa”就好办,把前面的"#" 砍掉,用getElementsById去查找便是!如果是"p span"意思是取得所有p元素中的span元素,我们先用document.getElementsByTagName("p")得到所有p元素,然后遍历里面的p,用currentP.getElementsByTagName("span")就行了。但这都是最理想的情况下,我们要怎样才知道调用这些API呢?#提醒我们用IE选择器,但如果#号是包含在引号中呢,如p["gh#erewf"],这里也有#号。因此我们必须处理一下字符串,如把两边的空白去掉,把里面不必要的空白去掉,里面的每个空白只占一个字符空间就是,让我们知道那些后代选择符就是,多余的没有必要。我们看jQuery是如何处理的:
提示:可修改后代码再运行!
一个很强大的正则,不过这样后代选择符就看不到了,jQuery在后面一定做了什么补救措施。事实上,jQuery许多东西都不是一步到位,一个方法调用另一个方法,最后不是怎的就解决了其实一下子转换为数组意义不大,后面还是要其他正则进行深加工,它们都需要进一步的正则匹配,分析当前的数组元素是ID选择符还是属性选择符。
看来大家喜欢jQuery,那我就举jQuery吧。其他类库也是用许多正则来处理字符串,来得到它们想要的信息。jQuery外面看起来很漂亮很清致,里面的实现真是相当复杂与恶心。或许John Resig是个心理阴暗的人,他公开源码但不想别人看懂,故意写成这个样子。我更怀疑源码中那些注释是给jQuery另两个作者看的……嘛,都瞎猜的。我们接着看实现流程,上面这些ID,class,name,attr什么的,基本上是用那两个选择器document.getElementById与element.getElementsByTagName加getAttribute搞定。犹其是那个标签选择器,每个元素节点都有这方法,我们才可以层层遴选正确的元素。每次我们取得一堆元素,然后逐一检测其属性与在父元素的位置,得到结果放到一个数组中,然后下一次再重复这样的步骤。有时,两个元素都有相同的子元素,我们就需要在这些元素做一些标记,如果没有这标记的才放进数组,有就跳过。至于这些怎样实现,这是jQuery源码要讲的。最后附上一个表:
开头字符 | 代表选择符 | 判断方法 |
---|---|---|
# | ID选择符 | /#((?:[wu00c0-uFFFF_-]|.)+)/ |
. | 类选择符 | /.((?:[wu00c0-uFFFF_-]|.)+)/ |
后代选择符 | /^ss*/ | |
[ | 属性选择符 | /[s*((?:[wu00c0-uFFFF_-]|.)+)s*(?:(S?=)s*(['"]*)(.*?)3|)s*]/ |
+ | 相邻选择符 | /^[>+~]/ |
~ | 兄长选择符 | /^[>+~]/ |
> | 亲子选择符 | /^[>+~]/ |
: | 伪类选择符 | /:((?:[wu00c0-uFFFF_-]|.)+)(?:((['"]*)((?:([^)]+)|[^2()]*)+)2))?/ |
, | 联合选择符 | /,/ |
字母 | 标签选择符 | /^((?:[wu00c0-uFFFF*_-]|.)+)/ |