`

Sizzle效率高的原因分析

阅读更多

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

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

getElementById 上下文只能是HTML文档 

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

 

getElementsByName,上下文只能是HTML文档

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

 

getElementsByClassName

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

 

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集合,后者只返回符合查询条件的第一个节点。很遗憾IE6、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)方法,它内部最耗性能的“编译”过程不会再耗太多性能了,直接取之前缓存的方法就可以了。我在想所谓“编译”的最大好处之一可能也就是便于缓存,所谓“编译”在这里可能也就可以理解成是生成预处理的函数存储起来备用。

 


如何打造高效的选择器?

jQuery选择器使用频率列表



 

正确使用选择器引擎对于提高页面性能起了至关重要的作用。使用合适的选择器表达式可以提高性能、增强语义并简化逻辑。在传统用法中,最常用的简单选择器包括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>





 

毋庸置疑 id是最快的, 因为节点较少 所以来看出class与tag的区别

 

测试二

复制代码
<div id = "demo" > 
  <ul> 
    <li> </li>
    <li></li > 
    <li> </li>
    <li></li > 
 </ul>
</div >
 



 



 

通过对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>
 



 

 

为什么差距这么大?

因为采用了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选择器有关的性能问题是尽量采用链式调用来操作缓存选择器结果集

因为每一个$()的调用都会导致一次新的查找,所以,采用链式调用和设置变量缓存结果集,减少查找,提升性能。

  • 大小: 7.8 KB
  • 大小: 7.8 KB
  • 大小: 9.2 KB
  • 大小: 16.1 KB
  • 大小: 27 KB
  • 大小: 18.8 KB
  • 大小: 10.5 KB
  • 大小: 13.3 KB
分享到:
评论

相关推荐

    Sizzle的

    通过阅读和分析Sizzle的代码,开发者可以学习到如何编写高效、跨浏览器的JavaScript代码,这对于任何前端开发者来说都是宝贵的技能。 总的来说,Sizzle是JavaScript开发中的一个重要工具,它使得我们可以用CSS选择...

    jQuery中的Sizzle引擎分析demo

    Sizzle引擎使得开发者能够使用类似于CSS的语法来选取DOM元素,极大地提高了网页开发的效率。在这个"jQuery中的Sizzle引擎分析demo"中,我们将深入探讨Sizzle的工作原理以及如何通过实例来理解它。 Sizzle引擎的设计...

    浅谈Sizzle的“编译原理”

    这种方法使得Sizzle在处理大量DOM操作时,能保持高性能。 总的来说,Sizzle的编译原理在于将CSS选择器的字符串形式转换为可执行的过滤逻辑,通过词法分析和生成匹配程序,实现对DOM树的高效查询。这种设计思路不仅...

    jQuery源码分析系列.pdf

    综上所述,这份关于jQuery源码分析的系列文档,系统地梳理了jQuery库的各个组成部分,并且详细探讨了内部实现机制,为开发者提供了宝贵的学习资源,使得开发者能更深入地理解和使用jQuery,提高前端开发的效率和质量...

    jQuery源码详细分析中文注释

    总的来说,这份《jQuery源码详细分析中文注释》是学习和研究jQuery源码的绝佳资料,通过深入阅读和理解注释,开发者不仅可以掌握jQuery的工作原理,还能提升JavaScript编程技巧,为自己的项目开发带来更高的效率和更...

    JQuery源码分析

    在`Jquery1.2.6源码分析`中,我们可以看到早期版本的Sizzle是如何通过正则表达式和遍历DOM树来实现高效的元素查找的。这个过程涉及到了对DOM API的深入理解和性能优化。 其次,jQuery的链式调用模式是其易于使用的...

    Jquery1.2.6源码分析教程

    Sizzle 使用正则表达式和遍历树的算法来提高性能,即使在旧版浏览器中也能保持良好的效率。 2. **链式调用**:jQuery 对象返回自身,使得多个操作可以连续调用,大大简化了代码。例如,`$("#element").hide()....

    Jquery源码分析.rar

    这种设计使得我们可以连续调用多个方法而无需创建新的jQuery实例,提高了代码的可读性和效率。 3. **事件处理**: jQuery对事件的处理进行了封装,提供了一致的API,使得跨浏览器的事件处理变得简单。它使用`...

    jQuery 源码分析

    《jQuery 源码分析——揭示JavaScript库的精髓》 jQuery,这个JavaScript库自2006年诞生以来,就以其简洁的API和强大的功能深受开发者喜爱,它极大地简化了DOM操作、事件处理、动画效果以及Ajax交互。本文将深入...

    Jquery源码分析

    此外,jQuery还实现了元素克隆、属性操作等功能,极大地提高了开发效率。 3. **事件(Events)**:jQuery对事件处理进行了封装,提供了一致的API,如`click()`、`mouseover()`等。同时,`bind()`、`unbind()`和`...

    jQuery-Selectors-源码.rar

    分析jQuery选择器的源码,不仅可以提升我们对CSS选择器的理解,还能让我们掌握更多JavaScript编程技巧,以及如何编写高性能的DOM操作代码。 总的来说,jQuery-Selectors-源码.zip提供了深入了解jQuery选择器内部...

    jQuery源码分析

    Sizzle引擎使用正则表达式和XPath策略,实现了高性能的选择器匹配。 三、事件处理 jQuery提供了一套简洁的事件处理API,如.on()、.off()、.trigger()等。这些方法可以方便地绑定、解绑和触发事件。jQuery还支持...

    jquery1.2.6源码分析

    jQuery的核心是它的选择器引擎Sizzle,它使得在DOM中查找元素变得极其简单。在jQuery 1.2.6中,`$()`函数就是入口,它可以接受CSS选择器字符串,返回一个jQuery对象。这个对象封装了DOM元素集合,提供了链式调用的...

    jquery 源码分析

    通过这个引擎,jQuery能快速准确地定位页面中的元素,极大地提高了开发效率。 DOM操作是jQuery的另一个重点。jQuery对象是对一组DOM元素的封装,提供了丰富的API供开发者使用,如`append()`, `prepend()`, `remove...

    jquery1.2.6源码分析rar + API

    源码中的Sizzle选择器引擎用于解析选择器,找到对应的DOM元素。 2. 函数节流与延迟:jQuery的$.fn.extend方法用于扩展jQuery对象的方法,其中throttle和delay函数实现了函数执行的节流和延迟,优化了性能。 3. ...

    novice to ninja jquery source code

    7. **性能优化**:分析jQuery源码有助于理解如何编写高性能的代码,比如元素集合的遍历、内存管理以及DOM操作的最佳实践。 在学习过程中,建议配合实际项目进行实践,尝试修改和扩展jQuery源码,这将加深对库的理解...

    锋利的jQuery源代码

    jQuery是Web开发中的一个关键工具,它简化了DOM操作、事件处理、动画效果和Ajax交互等多个方面的工作,极大地提高了开发效率。 jQuery的核心理念是“Write Less, Do More”,它通过简洁的API设计,使得复杂的...

    jquery-2.1.0-analysis:jquery源码分析

    此外,jQuery 2.1.0版本去除了对IE6-8的支持,这使得代码可以更轻量级,执行效率更高。然而,这也意味着开发者在使用此版本时需要确保目标浏览器支持现代的JavaScript特性和DOM API。 总的来说,通过分析jQuery ...

    Jquery源码

    分析jQuery源码有助于理解JavaScript和DOM的交互,以及如何编写高性能、易维护的前端代码。通过深入研究,开发者可以提升自己的编程技巧,并从中吸取灵感,应用于自己的项目中。 总结,jQuery的源码不仅是前端开发...

Global site tag (gtag.js) - Google Analytics