jQuery技术

全新选择器引擎 jQuery Sizzle初探

字号+ 作者: 来源: 2014-11-16 22:49 我要评论( )

这是一篇关于介绍jQuery Sizzle选择器的文章。在文中,作者试图用自己的语言配以适量的代码向读者展现出Sizzle在处理选择符时的流程原理,以及末了以少许文字给你展示出如何借用Siz

jQuery相比1.2的版本,在内部代码的构造上已经出现了巨大的变化,其之一便是模块的分发。随着jQuery被用来构建web app的场合愈来愈多,它的性能自然受到了大部分开发者的高度关注,它的内部实现机理又是如何,比如选择器的实现。

关于jQuery更多内容,欢迎访问: jQuery从入门到精通

Sizzle,作为一个独立全新的选择器引擎,出现在jQuery 1.3版本之后,并被John Resig作为一个开源的项目,可以用于其他框架:Mool, Dojo,YUI等。好了,现在来看为什么Sizzle选择器如此受欢迎,使它能够在常用dom匹配上都快于其他选择器而让这些框架们都垂青于它。(相关文章推荐:改变获取对象方式 万能的jQuery选择器

概要

一般选择器的匹配模式(包括jq1.2之前),都是一个顺序的思维方式,在需要递进式匹配时,比如$(‘div span’) 这样的匹配时,执行的操作都是先匹配页面中div然后再匹配它的节点下的span标签,之后返回结果。

Sizzle则采取了相反Right To Left的实现方式,先搜寻页面中所有的span标签,再其后的操作中才去判断它的父节点(包括父节点以上)是否为div,是则压入数组,否则pass,进入下一判断,最后返回该操作序列。另外,在很多细节上也进行了优化。

浅析源码

当我们给$符传递进一个参数(也可能是多个)时,此时它会根据参数的类型(domElement | string | fn | array)进入不同的流程,在此,重点看 string 类型的处理,因为只有它才可以触发Sizzle。首先调用正则匹配看是否为创建dom节点的操作,然后看是否为简单id匹配,这一步也由正则匹配完成,否则进入jQuery.fn.find()函数,由此进入Sizzle的天地。

当进入Sizzle时,一般情况下会配备三参:所要匹配的选择符,上下文,匹配的结果集。调用正则对传入的selector做一次”预匹配”.

让我们看一下一个简单选择器的实现过程:比如 div > p。我们要先找出符合条件的div[div],再找出符合条件的p[p],最后在上下文里[div]过滤出符合条件”>”的p[p];抽象一点的说法就是:在已知的上下文里,根据关系找出相应的节点。他们靠关系联系起来。那么对于选择器的操作也就是根据关系来分组。一次次缩小上下文,直到找出符合条件的节点。回到我们的话题,还是先看看这个令人费解的正则,相信你会有更好的分析方法,但是眼下,我还是一点点的拆分,让它表达的更清晰一点。先按照分组拆,即():

  1. ((?:\((?:\([^()]+\)|[^()]+)+\)|\  
  2. [(?:\[[^\[\]]*\]|['"][^'"]*['"]|  
  3. [^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])  
  4. (\s*,\s*)?((?:.|\r|\n)*) 

第一行还是有点长,用’|'拆分它:

  1. 1. (?:\((?:\([^()]+\)  
  2. 2.[^()]+)+\)  
  3. 3. \[(?:\[[^[\]]*\]  
  4. 4. [^[\]]+)+\]|\\.  
  5. 5.[^ >+~,(\[]+)+6.[>+~] 

对于如div > p。会得到数组结果['div','>','p']。对于更复杂的选择器,如div.classname > p.classname。会得到结果['div.classname','>','p.classname']。对于具有合并的‘,’,只是递归调用下获取结果再合并而已。过程开始变得简单起来。对于普通的解析过程,我们遵循着从左到右的顺序即可完成我们的目标。让我们总结下步骤:

1.先查找页面上所有的div

2.循环所有的div,查找每个div下的p

3.合并结果

Sizzle用了截然相反的步骤:

◆先查找页面上所有的p

◆循环所有的p,查找每个p的父元素

◆如果不是div,遍历上一层。

◆如果已经是顶层,排除此p。

◆如果是div,则保存此p元素。

由子元素来查找父元素,能得到更好的效率。打破常规思维后不仅步骤更简单了,而且效率上也得到了些许提升。所有的选择器都可以这样解析吗?不是,采用right -> left的顺序是有前提条件的:没有位置关系的约束。比如如下这段html:

  1. <div>p1contentp2content </div>   
  2. <div>p3contentp4content </div> 

对于选择器:$(“div p:first”)只会返回["#p1"]。而$(“div p:first-child”)则返回["#p1", "#p3"]。两者的区别在于位置filter的结果依赖于它前面的selector解析的结果,而其它 filter,只依赖于当前元素本身,就可以判断它是否满足filter。那Sizzle是通过什么来判定进入哪一个流程呢,答案是origPOS的正则匹配,origPos指向了Expr中match对象的POS属性,而POS中存储了五花八门的位置类约束,如下:

  1. /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/     //POS的值
  2.  

这样一来,第一步的流程判断就已明朗。

当处于1的情况时:

首先根据需要对当前处理的A数组元素进行一系列修正操作(Expr.relative主刀)。然后调用posProcess函数对修复后的元素进行匹配.

其中,还需一层判断,如果有层级约束,eg ‘>,’:input’ 会转化为 ‘> :input’,因为在最初调用chunker进行预匹配的时候,这些是会被分割为单个数组元素的,但在这里需要将它们做一次合并,这是由posProcess所处理的数据格式所决定的。

  1. //如果存在伪类选择符,从selector中移除,并保存在later中   
  2. // 这样一来,匹配对象便分离出来:selector(简单选择符存储器)和later(伪类选择符存储器)。   
  3. while ( (match = Expr.match.PSEUDO.exec( selector )) )   
  4. {    later += match[0];    selectorselector = selector.replace( Expr.match.PSEUDO, "" ); }   
  5. //构造selector,并调用Sizzle进行匹配,将结果存储在tmpSet中  
  6. selector = Expr.relative[selector] ? selector + "*" : selector;   
  7. for ( var i = 0l = root.length; i &lt;  ; i++ )  
  8.  {    Sizzle( selector, root[i], tmpSet ); }  
  9.  // 最后便是filter的过滤 return Sizzle.filter( later, tmpSet ); 

源码片段b: — 对预匹配后的数组A中元素的处理:

  1. //这个为特例,被正则分割的A数组长度为2,则合并数组元素,上下文则原封不动为Sizzle传递进来的context。  
  2.  if ( parts.length === 2 &amp;&amp; Expr.relative[ parts[0] ] )   
  3. {      
  4. // 完成一次匹配, 由posProcess 内部调用 filter进行匹配      
  5. // 但在匹配前,完成了一次连接选择符的操作      
  6. // 存入set,注 set 当前还不是最终的结果,其这里的set和上面的tmpSet一样,都是一个"暂时性"的结果集      
  7. set = posProcess( parts[0] + parts[1], context ); 

源码片段c: — 如果存在位置约束关系, 正向匹配。

  1. set = Expr.relative[ parts[0] ] ?      
  2. [ context ] :      
  3. // 否则对队列首元素进行一次简单匹配操作      
  4. Sizzle( parts.shift(), context ); 

分析Expr.relative,可以看出,它包含了4种dom元素间关系的判断,分别是 “+”, “>”, “”, “~”。每一轮的匹配,都会先判断A数组的首元素是不是代表tag间关系符(+,>等) ,而做后续处理.同时对A数组进行循环,依次做类似的处理。源码片段d — 对A数组(parts)的循环处理及后续。

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 7个有用的jQuery小技巧

    7个有用的jQuery小技巧

    2016-02-26 13:02

  • jQuery制作select双向选择列表

    jQuery制作select双向选择列表

    2016-02-26 11:00

  • 全面详细的jQuery常见开发技巧手册

    全面详细的jQuery常见开发技巧手册

    2016-02-26 10:02

  • 强大的jQuery移动插件Top 10

    强大的jQuery移动插件Top 10

    2016-02-25 09:05

网友点评
p