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

相關文章
相關標籤/搜索