一小段jQuery代碼的分析與最佳化

來源:互聯網
上載者:User
文章目錄
  • 尋找原因
  • 基本最佳化
  • 注意選取器
  • 不要啥都讓javascript來做
  • 最後的補充

今天剛回家,QQ群裡就看到有人求助最佳化一段jQuery代碼,簡單看了一下,發現如果對jQuery這東西只停留在用的層面,而不知其具體實現的話,真的很容易用出問題來。這也是為什麼近期我一直不怎麼推崇用jQuery,這架構的API設定就有誤導人們走上歧途之嫌。

需要最佳化的代碼大致是這樣的,也不方便直接把人家的代碼複製過來,就大概地表達下意思:

$.fn.beautifyTable = function(options) {    //定義預設配置項,再用options覆蓋    return this.each(function() {        var table = $(this),            tbody = table.children('tbody'),            tr = tbody.children('tr'),            th = tbody.children('th'),            td = tbody.children('td');                    //單獨內容的class        table.addClass(option.tableClass);        th.addClass(options.headerClass); //1        td.addClass(options.cellClass); //2        //奇偶行的class        tbody.children('tr:even').addClass(options.evenRowClass); //3        tbody.children('tr:odd').addClass(options.oddRowClass); //4        //對齊        tr.children('th,td').css('text-align', options.align); //5        //添加滑鼠懸浮        tr.bind('mouseover', addActiveClass); //6        tr.bind('mouseout', removeActiveClass); //7        //點擊變色        tr.bind('click', toggleClickClass); //8     });};

總的來說,這段代碼不錯,思路清晰,邏輯明確,想要做什麼也通過注釋說得很明白了。但是按作者的說法,當表格中有120行時,IE已經反映指令碼已耗用時間過長了。顯然從表現來看,這個函數的效率不高,甚至說極其低下。

尋找原因

於是,開始從代碼層面進行分析,這是一個標準的jQuery外掛程式式的函數,有個典型的return this.each(function() { ... };);形式的代碼,如果作者寫下這段代碼的時候,不是照本宣科不經思考的話,就應該意識到jQuery的一個函數幹了什麼事。

簡單來說,jQuery.fn下的函數,絕大部分是一個each的調用,所謂each,自然是對選擇出來的元素進行了遍曆,並對某個元素進行了指定的操作。那麼看看上面一段代碼,進行了多少的遍曆,在此就假設只選擇了120行,每一行有6列,另加上1行的表頭吧:

  1. 遍曆th,添加headerClass,元素數為6。
  2. 遍曆td,添加cellClass,元素數為6*120=720。
  3. 從所有tr中找出奇數的,需要對所有tr進行一次遍曆,元素數為120。
  4. 遍曆奇數的tr,添加evenRowClass,元素數為120/2=60。
  5. 從所有tr中找出偶數的,需要對所有tr進行一次遍曆,元素數為120。
  6. 遍曆偶數的tr,添加oddRowClass,元素數為120/2=60。
  7. 遍曆所有th和td,添加text-align,元素數為120*6+6=726。
  8. 遍曆所有tr,添加mouseover事件,元素數為120。
  9. 遍曆所有tr,添加mouseout事件,元素數為120。
  10. 遍曆所有tr,添加click事件,元素數為120。

為了方便,我們簡單地假設,在遍曆中訪問一個元素耗時為10ms,那麼這個函數一共用了多少時間呢?這個函數共遇上了2172個元素,耗時21720ms,即21秒,顯然IE確實應該報指令碼執行過久了。

基本最佳化

知道了效率低下的原因,要從根本上進行解決,自然要想方設法來合并迴圈,初略一看,按照上邊代碼中注釋裡的數字,至少以下幾點是可以合并的:

  • 3和4可以合并為一次迴圈,從120+60+120+60變為120,減少了240。
  • 1、2和5可以合并為一次迴圈,從6+720+726變為726,減少了726。
  • 6、7、8可以合并為一次迴圈,從120+120+120變為120,減少了240。
  • 進一步的,3、4和6、7、8一樣可以合并為一次迴圈,繼續減少了120。

累加一下,我們一共減少了240+726+240+120=1326次元素操作,總計13260ms。在最佳化之後,我們的函數耗時變為21720-13260=8460ms,即8s。

注意選取器

到這裡可能會有一個疑問,從表格的結構上來說,所有的th和td元素肯定都在tr之內,那麼為什麼不將1、2、5這三步的迴圈同樣放到對tr的迴圈中,形成一個嵌套的迴圈,這樣不是更加快速嗎?

這裡之所以沒有這麼做,主要有2個原因:

其一,無論將1、2、5這三者放在哪裡,都不會減少對所有th和td元素的一次訪問。

另一方面,$('th,td')這個選取器,在sizzle中會被翻譯成2次getElementsByTagName函數的調用,第一次擷取所有th,第二次擷取所有td,然後進行集合的歸併。由於getElementsByTagName是內建函數,在此可以認為該函數是不帶迴圈的,即複雜度為O(1),同樣集合的歸併使用Array的相關函數,是對記憶體的操作,複雜度同樣為O(1)。

反之,如果在對tr元素的迴圈中再採用$('th,'td)這個選取器,則是在tr元素上調用2次getElementsByTagName,由於無論在哪個元素上調用該函數,函數執行的時間是相同的,因此在迴圈tr時使用,反而多出了119*2次的函數調用,效率不升反降。

可見,對sizzle選取器的基本知識,也是協助最佳化jQuery代碼的很重要的一方面。

不要啥都讓javascript來做

根據前面的基本的最佳化,已經將時間從21秒降到了8秒,但是8秒這個數字顯然是無法接受的。

再進一步分析我們的代碼,事實上,迴圈遍曆是語言層面上的內容,其速度應該是相當快的。而針對每個元素所做的操作,是jQuery提供的函數,相比遍曆來說,才是佔去大部分資源的主子。如果說遍曆中訪問元素用時是10ms的話,不客氣地說執行一個addClass至少是100ms層級的消耗。

因此,為了進一步地最佳化效率,就不得不從減少對元素的操作入手。再仔細地回審代碼,發現這個函數有著非常多的對樣式的修改,其中至少包括了:

  • 給所有th加上class。
  • 給所有td加上class。
  • 給tr分奇偶行加上class。
  • 給所有th和td加上一個text-align樣式。

而事實上我們知道,CSS本身就擁有子代選取器,而瀏覽器原生對CSS的解析,效率遠遠高於讓javascript去給元素一一加上class。

所以,如果對CSS是可控的,那麼這個函數就不應該擁有headerClass、cellClass這兩個配置項,而是儘可能地在CSS中進行配置:

.beautiful-table th { /* headerClass的內容 */ }.beautiful-table td { /* cellClass的內容 */ }

再者,對於tr的奇偶行樣式,在部分瀏覽器下可以使用:nth-child偽類來實現,這方面可以利用特性探測,僅在不支援該偽類的瀏覽器中使用addClass添加樣式。當然如果你僅僅想對IE系列進行最佳化的話,這一條可以忽略了。

對於:nth-child偽類的探測,可以用以下的思路來進行:

  1. 建立一個stylesheet,再建立一條規則,如#test span:nth-child(odd) { display: block; }。
  2. 建立相應的HTML結構,一個id為test的div,內部放置3個span。
  3. 將stylesheet和div一同加入的DOM樹中。
  4. 查看第1和第3個span的運行期display樣式,如果是block,則表明支援該偽類。
  5. 刪除建立的stylesheet和div,別忘了緩衝探測的結果。

最後,對於給所有th和td元素添加text-align樣式,也是可以通過css進行最佳化的。既然不知道添加的是哪個align,那麼就多寫幾個樣式:

/* CSS樣式 */.beautiful-table-center th,.beautiful-table-center td { text-align: center !important; }.beautiful-table-right th,.beautiful-table-right td { text-align: right !important; }.beautiful-table-left th,.beautiful-table-left td { text-align: left !important; }/* javascript */table.addClass('beautiful-table-' + options.align);

當然,上面所說的最佳化,是建立在對CSS有控制權的情況下的,如果本身無法接觸到CSS樣式,比如這是一個通用的外掛程式函數,會被完全無法控制的第三方使用,那麼怎麼辦呢?也不是完全沒有辦法:

  1. 去找頁面裡的所有CSS規則,比如document.styleSheets。
  2. 遍曆所有規則,把配置項中的headerClass、cellClass等拿出來。
  3. 提取需要的幾個class中的所有樣式,再自己組裝成新的選取器,如beautiful-table th。
  4. 使用建立出來的選取器,產生新的stylesheet,加入到DOM樹中。
  5. 那麼只給table加上beautiful-table這個class就搞定了。

當然上面的做法其實也蠻消耗時間的,畢竟又要遍曆stylesheet,又要建立stylesheet。具體是不是對效率提升有很大的協助,則依據頁面的規模會有不同的效果,是否使用就要看函數設計人員的具體需求了,這裡也就是提一種策略。

總的來說,通過儘可能少地執行javascript,將更多的樣式化的任務交給CSS,則瀏覽器的渲染引擎來完成,又可以進一步地最佳化該函數,假設對addClass、css的調用需要100ms的話,此次最佳化直接消滅了原有120+726=846次的操作,節約了84600ms的時間(當然有誇張的成分,但是對整個函數的消耗來說,這個確實是很大的一塊)。

最後的補充

這篇文章,僅僅是想在jQuery的各個實現的層面上來進行最佳化,只涉及到了對jQuery整個運行過程的分析、細節介紹和最佳化方向,並沒有提到一些基本之基本的最佳化方法,比如:

  • 先將整個table從DOM樹中移除,完成所有的操作之後再放回DOM,減少repaint。
  • 將mouseover和mouseout改為mouseenter和mouseleave,減少因為下正確的事件冒泡模型導致的重複的事件函數的執行。
  • 對於th、td之類單純元素的選擇,優先考慮使用原生的getElementsByTagName,消滅sizzle分析選取器的時間。

最後,這篇文章只是想說明,對於前端開發人員,雖然瀏覽器可能是個黑盒,但是很多架構、工具、庫都是開放的,在使用之前如果可以進行一定程度的瞭解,必然有助於個人的技術提升和最終產品的品質最佳化,“知其然而不知其所以然”是非常忌諱的情況。

 

本文永久地址:http://www.otakustay.com/jquery-code-optimization-1/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.