首頁  >  文章  >  web前端  >  JQuery each()函數如何最佳化循環DOM結構的效能_jquery

JQuery each()函數如何最佳化循環DOM結構的效能_jquery

WBOY
WBOY原創
2016-05-16 17:47:001149瀏覽

如果對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行的表頭吧:
遍歷th,加上headerClass,元素數為6。
遍歷td,新增cellClass,元素數為6*120=720。
從所有tr中找出奇數的,需要對所有tr進行一次遍歷,元素數為120。
遍歷奇數的tr,加入evenRowClass,元素數為120/2=60。
從所有tr中找出偶數的,需要對所有tr進行一次遍歷,元素數為120。
遍歷偶數的tr,加入oddRowClass,元素數為120/2=60。
遍歷所有th和td,加入text-align,元素數為120*6 6=726。
遍歷所有tr,新增mouseover事件,元素數為120。
遍歷所有tr,新增mouseout事件,元素數為120。
遍歷所有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來做。

根據前面的基本的最佳化,已經將時間從2​​1秒降到了8秒,但8秒這個數字顯然是無法接受的。
再進一步分析我們的程式碼,事實上,循環遍歷是語言層面上的內容,其速度應該是相當快的。而針對每個元素所做的操作,是jQuery提供的函數,相較於遍歷來說,才是佔去大部分資源的主子。如果說遍歷中存取元素用時是10ms的話,不客氣地說執行一個addClass至少是100ms等級的消耗。

因此,為了進一步地優化效率,就不得不從減少對元素的操作入手。再仔細地回審程式碼,發現這個函數有著非常多的對樣式的修改,其中至少包含了:
給所有th加上cla​​ss。
為所有td加上cla​​ss。
為tr分奇偶行加上cla​​ss。
為所有th和td加上一個text-align樣式。
而事實上我們知道,CSS本身就擁有子代選擇器,而瀏覽器原生對CSS的解析,效率遠高於讓javascript去給元素一一加上cla​​ss。

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

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


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

對於:nth-​​child偽類的探測,可以用以下的思路來進行:建立一個stylesheet,再建立一條規則,如#test span:nth-child(odd) { display: block; } 。建立對應的HTML結構,一個id為test的div,內部放置3個span。

將stylesheet和div一同加入的DOM樹中。查看第1和第3個span的運行期display樣式,如果是block,則表示支援該偽類。刪除已建立的stylesheet和div,別忘了快取探測的結果。最後,對於為所有th和td元素添加text-align樣式,也是可以透過css來優化的。既然不知道要加入的是哪個align,那就多寫幾個樣式: 程式碼如下:


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


當然,上面所說的優化,是建立在對CSS有控制權的情況下的,如果本身無法接觸到CSS樣式,例如這是一個通用的插件函數,會被完全無法控制的第三方使用,那麼怎麼辦呢?也不是完全沒有辦法:
去找頁面裡的所有CSS規則,例如document.styleSheets。遍歷所有規則,把配置項中的headerClass、cellClass等拿出來。提取需要的幾個class中的所有樣式,再自己組裝成新的選擇器,如beautiful-table th。使用建立出來的選擇器,產生新的stylesheet,加入DOM樹。那麼只給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分析選擇器的時間。
最後,這篇文章只是想說明,對於前端開發人員,雖然瀏覽器可能是個黑盒,但是很多框架、工具、庫都是開放的,在使用之前如果可以進行一定程度的了解,必然有助於個人的技術提升和最終產品的品質優化,「知其然而不知其所以然」是非常忌諱的情況。
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn