jQuery 2.0.3 源码分析Sizzle引擎 - 高效查询

 

为何Sizzle很高效?javascript

首先,从处理流程上理解,它老是先使用最高效的原生方法来作处理css

HTML文档一共有这么四个API:html

getElementById 上下文只能是HTML文档 

浏览器支持状况:IE 6+, Firefox 3+, Safari 3+, Chrome 4+, and Opera 10+;java

 

getElementsByName,上下文只能是HTML文档

浏览器支持状况:IE 6+, Firefox 3+, Safari 3+,Chrome 4+, and Opera 10+;jquery

 

getElementsByClassName

浏览器支持状况:IE 9+, Firefox 3+, Safari4+, Chrome 4+, and Opera 10+;web

 

getElementsByTagName

上下文能够是HTML文档,XML文档及元素节点。算法

 

高级API:数组

浏览器支持状况:IE 8+, Firefox 3.5+, Safari 3+, Chrome 4+, and Opera 10+;浏览器

querySelector 将返回匹配到的第一个元素,若是没有匹配的元素则返回 Null
querySelectorAll 返回一个包含匹配到的元素的数组,若是没有匹配的元素则返回的数组为空

 

浏览器内置的css选择符查询元素方法,比getElementsByTagName和getElementsByClassName效率要高不少缓存

前者接收一个CSS选择器字符串参数并返回一个NodeList类数组对象而不是返回HTML集合,后者只返回符合查询条件的第一个节点。很遗憾IE六、7不支持这两个API。

性能测试参考:http://jsperf.com/queryselectorall2

总的来讲仍是 document.getElementById 速度最快


Sizzle原理:

  1. 浏览器原生支持的方法,效率确定比Sizzle本身js写的方法要高,优先使用也能保证Sizzle更高的工做效率,在不支持querySelectorAll方法的状况下,Sizzle也是优先判断是否是能够直接使用getElementById、getElementsByTag、getElementsByClassName等方法解决问题。
  2. 相对复杂的状况,Sizzle老是选择先尽量利用原生方法来查询选择来缩小待选范围,而后才会利用前面介绍的“编译原理”来对待选范围的元素逐个匹配筛选。进入到“编译”这个环节的工做流程有些复杂,效率相比前面的方法确定会稍低一些,但Sizzle在努力尽可能少用这些方法,同时也努力让给这些方法处理的结果集尽可能小和简单,以便得到更高的效率。
  3. 即使进入到这个“编译”的流程,Sizzle还作了咱们前面为了优先解释清楚流程而暂时忽略、没有介绍的缓存机制。Sizzle.compile是“编译”入口,也就是它会调用第三个核心方法superMatcher,compile方法将根据selector生成的匹配函数缓存起来了。还不止如此,tokenize方法,它其实也将根据selector作的分词结果缓存起来了。也就是说,当咱们执行过一次Sizzle (selector)方法之后,下次再直接调用Sizzle (selector)方法,它内部最耗性能的“编译”过程不会再耗太多性能了,直接取以前缓存的方法就能够了。我在想所谓“编译”的最大好处之一可能也就是便于缓存,所谓“编译”在这里可能也就能够理解成是生成预处理的函数存储起来备用。

整个过程在sizzle源码分解都有详细的流程分解,还有缓存机制,XML,伪选择器,后期在补上

 


如何打造高效的选择器?

jQuery选择器使用频率列表

imageimage

正确使用选择器引擎对于提升页面性能起了相当重要的做用。使用合适的选择器表达式能够提升性能、加强语义并简化逻辑。在传统用法中,最经常使用的简单选择器包括ID选择器、Class选择器和类型标签选择器。其中ID选择器是速度最快的,这主要是由于它使用JavaScript的内置函数getElementById();其次是类型选择器,由于它使用JavaScript的内置函数getElementsByTag();速度最慢的是Class选择器,其须要经过解析 HTML文档树,而且须要在浏览器内核外递归,这种递归遍历是没法被优化的。

Class选择器在文档中使用频率靠前,这无疑会增长系统的负担,由于每使用一次Class选择器,整个文档就会被解析一遍,并遍历每一个节点。

 

基本的几个选择器的测试

性能测试网址

JSPerf (http://jsperf.com/)

Dromaeo (http://dromaeo.com/)

测试一

<div id="text">
     <input id='aaron'  class="aaron"  type="checkbox" name="readme" value="Submit" 
</div>

image

image

毋庸置疑 id是最快的, 由于节点较少 因此来看出class与tag的区别

 

测试二

<div id = "demo" > 
  <ul> 
    <li> </li>
    <li></li > 
    <li> </li>
    <li></li > 
 </ul>
</div >

image

image

经过对sizzle分析得知都选择器是从右向左匹配, $("#demo li:nth-child(1)") 这句将先匹配全部 li元素,在匹配#demo $("#demo").find("li:nth-child(1)") 而这里则先匹配#demo,再从中找匹配li,匹配范围缩短,效率明显提高

 

测试三

<div id="text">
  <p>
     <input type="text" />
  </p>
  <div class="aaron">
     <input type="checkbox" name="readme" value="Submit" />
     <p>Sizzle</p>
  </div>
</div>

imageimage

为何差距这么大?

由于采用了CSS的属性表达式,因此Sizzle用.querySelectorAll()来查找元素

$(‘input:text’),采用了jQuery自定义的选择器表达式:text,.querySelectorAll()方法没法解析

因此,在jqury中,一些选择器表达式广泛快于另一些选择器表达式,把选择器中的伪类移到相应的方法中能够加速查找页面文档dom元素的时间

为了简单起见,咱们把jQuery中用.getElementById (),.getElementsByTagName(),.getElementsByClassName() 这3个方法的结合来查找元素称为:循环和检验(loop and test)过程。

 

测试总结:

图形测试很简单,每秒执行的操做,所以,数值越高,执行效率越好,表明执行时间越短,性能越好

在现代浏览器中,(Chrome 12, Firefox4, and Safari 5,IE 8+) ,CSS选择器表达式底层采用.querySelectorAll()方法,很好的实现了优点,平均而言,大概是自定义选择器表达式性能表现的2倍。可是,在ie7中,这两个选择器的性能表现差很少,这是由于在ie7环境下,Sizzle都采用了循环和检验(loop and test)过程累找到相应的元素,(由于ie7不支持.querySelectorAll()方法。),因此在编写jQuery的选择器函数进行事件注册时,要特别注意,可能你的代码在ie8以上执行正确,但在ie7中,$()函数返回的object.length将是0

 


选择器性能优化建议

http://learn.jquery.com/performance/optimize-selectors/

 

第一,多用ID选择器 , 老是从#id选择器来继承

多用ID选择器,这是一个明智的选择。即便添加"在"ID选择器,也能够从父级元素中添加一个ID选择器,这样就会缩短节点访问的路程。

这是jQuery选择器的一条黄金法则。jQuery选择一个元素最快的方法就是用ID来选择了

$('#content').hide();
 

或者从ID选择器继承来选择多个元素

$('#content p').hide();

再如

$("#container").find("div.robotarm");

效率更高,那是由于$("#container")是不须要通过Sizzle选择器引擎处理的,jquery对仅含id选择器的处理方式是直接使用了浏览器的内置函数document.getElementById(),因此其效率是很是之高的。

特征性

使一个选择器的右边更具备特征,相对而言,选择器的左边能够少一些特征性。

// unoptimized  优化前
$( "div.data .gonzalez" );
  
 // optimized     优化后
$( ".data td.gonzalez" );

  再选择器的右边尽量使用"tag.class"类型的选择符,在选择器的左边直接使用标签选择符或类选择符便可。

  (相似于css选择器,其匹配算法是从右至左的)

避免过分的约束
$(".data table.attendees td.gonzalez");
  
// better: drop the middle if possible   尽量移除掉中间的
 $(".data td.gonzalez");

一个更为“扁平”的DOM结构,会使得选择器引擎在寻找元素时通过的层次数更少,所以这样也是有利于提升选择器的性能的。

避免使用全局的选择器

一个会被在多处地方成功匹配的选择器可能会消耗更多的性能

$(".buttons > *");  // extremely expensive
 $(".buttons").children();  // much better
  
 $(".gender :radio");  // implied universal selection
 $(".gender *:radio"); // same thing, explicit now
 $(".gender input:radio"); // much better

 

第二,少直接使用Class选择器。

可使用复合选择器,例如使用tag.class代替.class。文档的标签是有限的,可是类能够拓展标签的语义,那么大部分状况下,使用同一个类的标签也是相同的。

固然,应该摒除表达式中的冗余部分,对于没必要要的复合表达式就应该进行简化。例如,对于#id2 #id1 或者 tag#id1表达式,不妨直接使用#id1便可,由于ID选择器是唯一的,执行速度最快。使用复合选择器,相反会增长负担。

在class前面使用tag

jQuery中第二快的选择器就是tag选择器(如$(‘head’)),由于它和直接来自于原生的Javascript方法getElementByTagName()。因此最好老是用tag来修饰class(而且不要忘了就近的ID)

var receiveNewsletter = $('#nslForm input.on');

jQuery中class选择器是最慢的,由于在IE浏览器下它会遍历全部的DOM节点。尽可能避免使用class选择器。也不要用tag来修饰ID。下面的例子会遍历全部的div元素来查找id为’content’的那个节点:

var content = $('div#content'); // 很是慢,不要使用

用ID来修饰ID也是多此一举:

var traffic_light = $('#content #traffic_light'); // 很是慢,不要使用

 

第三,多用父子关系,少用嵌套关系。

例如,使用parent>child代替parent child。由于">"是child选择器,只从子节点里匹配,不递归。而" "是后代选择器,递归匹配全部子节点及子节点的子节点,即后代节点。

 

下面六个选择器,都是从父元素中选择子元素。你知道哪一个速度最快,哪一个速度最慢吗?

$('.child', $parent)
$parent.find('.child')
$parent.children('.child')
$('#parent > .child')
$('#parent .child')
$('.child', $('#parent'))

 

1. 给定一个DOM对象,而后从中选择一个子元素。jQuery会自动把这条语句转成$.parent.find('child'),这会致使必定的性能损失。它比最快的形式慢了5%-10%。

$('.child', $parent)

 

3. 这条是最快的语句。.find()方法会调用浏览器的原生方法(getElementById,getElementByName,getElementByTagName等等),因此速度较快。

$parent.find('.child')

 

3. 这条语句在jQuery内部,会使用$.sibling()和javascript的nextSibling()方法,一个个遍历节点。它比最快的形式大约慢50%

parent.children('.child'):

 

4. jQuery内部使用Sizzle引擎,处理各类选择器。Sizzle引擎的选择顺序是从右到左,因此这条语句是先选.child,而后再一个个过滤出父元素#parent,这致使它比最快的形式大约慢70%。

$('#parent > .child'):

 

5 这条语句与上一条是一样的状况。可是,上一条只选择直接的子元素,这一条能够于选择多级子元素,因此它的速度更慢,大概比最快的形式慢了77%。

$('#parent .child'):

 

6 jQuery内部会将这条语句转成$('#parent').find('.child'),比最快的形式慢了23%。

$('.child', $('#parent')):

 

因此,最佳选择是$parent.find('.child')。并且,因为$parent每每在前面的操做已经生成,jQuery会进行缓存,因此进一步加快了执行速度。

 

第四,缓存jQuery对象。

若是选出结果不发生变化的话,不妨缓存jQuery对象,这样就能够提升系统性能。养成缓存jQuery对象的习惯可让你在不经意间就可以完成主要的性能优化。
下面的用法是低效的for (i = 0 ; i < 10000; i ++ ) ... {   
      var a= $( ' .aaron' );   
     a.append(i);   
}
而使用下面的方法先缓存jQuery对象,则执行效率就会大大提升。
var a= $( ' .aaron' );   
for (i = 0 ; i < 10000 ; i ++ ) ... {   
     a.append(i);   
}

 

经过链式调用,采用find(),end(),children(),has,filter()等方法,来过滤结果集,减小$()查找方法调用,提高性能

$('#news').find('tr.alt').removeClass('alt').end().find('tbody').each(function() {
        $(this).children(':visible').has('td').filter(':group(3)').addClass('alt');
   });
 

修改下,缓存结果集示例:

var $news = $('#news');
 $news.find('tr.alt').removeClass('alt');
 $news.find('tbody').each(function() {
         $(this).children(':visible').has('td').filter(':group(3)').addClass('alt');
  });

经过声明$news变量缓存$(‘#news’)结果集,从而提高后面结果集对象调用方法的性能。

 

总的来讲,作为一个常见的规则,咱们应该尽可能使用符合CSS语法规范的CSS选择器表达式,以此来避免使用jQuery自定义的选择器表达式

在jQuery选择器性能测试方面,能够采用http://jsperf.com/这个在线工具来检验哪一种编写方法对性能的改进影响更大

跟jQuery选择器有关的性能问题是尽可能采用链式调用来操做缓存选择器结果集

由于每个$()的调用都会致使一次新的查找,因此,采用链式调用和设置变量缓存结果集,减小查找,提高性能。

 

参考网址

http://www.artzstudio.com/2009/04/jquery-performance-rules/

http://zhangqi.im/webdevelopment/jquery-performance-optimization-guidelines.html

http://www.aliued.cn/2013/02/28/jquery%E9%80%89%E6%8B%A9%E5%99%A8%E6%8E%A2%E8%AE%A8%E8%BF%9B%E9%98%B6.html

相关文章
相关标签/搜索