|
|
|
|
|
使用JavaScript或jQuery將元素(element)追加(append)到DOM,是前端開(kāi)發(fā)人員常常用到的技術(shù),不過(guò)雖然方法多種多樣,你可能不知道哪種方法才是最快最有效的。在本文中,將通過(guò)對(duì)比多種實(shí)現(xiàn)方法,找出最快最有效的方法。
本文演示中,我們假設(shè)我們的任務(wù)是將 10,000 個(gè) div
附加到網(wǎng)頁(yè)的正文中。
1、最簡(jiǎn)單直接的方法
大多數(shù)人,會(huì)這樣做:
for (var i=0; i<10000; i++) {
$("BODY").append($("<div />").addClass("test-div"));
}
這種方法簡(jiǎn)單直接明了,所以大家都喜歡使用。
此代碼產(chǎn)生的平均運(yùn)行時(shí)間為779.463ms。這意味著將這些元素附加到 DOM
需要超過(guò)四分之三秒的時(shí)間。
那么這里的問(wèn)題是什么?
好吧,有一些 - 太多的重排和太多的 jQuery 對(duì)象。我們將在下一步中解決后者,所以讓我們?cè)谶@里看看回流。
簡(jiǎn)單來(lái)說(shuō),reflow就是瀏覽器需要對(duì)網(wǎng)頁(yè)進(jìn)行處理和繪制的時(shí)候,是最昂貴的瀏覽器進(jìn)程之一。提高應(yīng)用程序性能的最簡(jiǎn)單方法之一是盡量減少回流次數(shù),這就是我們要做的。
許多不同的操作都可能導(dǎo)致回流,并且對(duì)于每個(gè)瀏覽器來(lái)說(shuō)都不一樣。將元素附加到 DOM 會(huì)導(dǎo)致重排。因此,我們的代碼片段的明顯問(wèn)題是我們正在生成 10,000 次回流。有些瀏覽器可能會(huì)智能地處理這個(gè)問(wèn)題并降低一些成本,但我們可以做得更好。
2、修復(fù)回流問(wèn)題
讓我們通過(guò)創(chuàng)建一個(gè)尚未添加到 DOM
的節(jié)點(diǎn)來(lái)解決我們的回流問(wèn)題。然后我們將把我們的 div
附加到這個(gè)節(jié)點(diǎn)上。最后,我們會(huì)將這個(gè)單個(gè)節(jié)點(diǎn)附加到應(yīng)該觸發(fā)單個(gè)回流的 DOM
中。
var $c = $("<div />").addClass("container");
for (var i=0; i<10000; i++) {
$c.append($("<div />").addClass("test-div"));
}
$("BODY").append($c);
代碼平均運(yùn)行時(shí)間現(xiàn)在是432.524ms。這有點(diǎn)好,但仍然不是很好,我們可以做很多改進(jìn)。
這里的問(wèn)題是我們?nèi)匀粍?chuàng)建了太多的 jQuery 對(duì)象。即使我們已經(jīng)解決了重排問(wèn)題,創(chuàng)建 jQuery 對(duì)象還是有很多開(kāi)銷。當(dāng)然,對(duì)于單個(gè)對(duì)象,jQuery 的便利性遠(yuǎn)遠(yuǎn)超過(guò)了對(duì)性能的最小影響。但是,如果我們要處理 10,000 個(gè)元素,那么效率低下的情況就非常明顯了。
讓我們看看當(dāng)我們完全跳過(guò)使用 jQuery 并用純 JavaScript 編寫(xiě)它時(shí)會(huì)發(fā)生什么。
var c = document.createDocumentFragment();
for (var i=0; i<10000; i++) {
var e = document.createElement("div");
e.className = "test-div";
c.appendChild(e);
}
document.body.appendChild(c);
令人難以置信的是,這種變化將我們的總運(yùn)行時(shí)間從432.524ms 減少到了16.237ms。這是有道理的,因?yàn)閯?chuàng)建對(duì)象實(shí)際上需要 jQuery 一些時(shí)間。讓我們看看創(chuàng)建單個(gè) jQuery 對(duì)象與使用原生 JavaScript 創(chuàng)建元素需要多長(zhǎng)時(shí)間。
console.time("jquery div");
var jqDiv = $("<div />");
console.timeEnd("jquery div");
// jquery div: 0.272ms
console.time("js div");
var jsDiv = document.createElement("div");
console.timeEnd("js div");
// js div: 0.006ms
這是一個(gè)顯著的差異 - 0.006ms與0.272ms。這種比較的目的并不是說(shuō) jQuery 不好,而是作為開(kāi)發(fā)人員你應(yīng)該知道什么時(shí)候使用它,什么時(shí)候不使用它。jQuery 提供了很多我們不需要的功能,因此以如此高的成本創(chuàng)建 10,000 個(gè) jQuery 對(duì)象是沒(méi)有意義的。
3、使用字符串而不是節(jié)點(diǎn)
我們實(shí)際上不必創(chuàng)建 10,000 個(gè)節(jié)點(diǎn)。相反,我們可以看到當(dāng)我們使用一長(zhǎng)串 HTML 時(shí)會(huì)發(fā)生什么。
var s = "";
for (var i=0; i<10000; i++) {
s += "<div class=\"test-div\"></div>";
}
$("BODY").append(s);
運(yùn)行時(shí)間需要69.874ms,它的性能比我們?cè)瓉?lái)的要好得多,但不如純 JavaScript 版本好。但是,我們可以做一點(diǎn)小改進(jìn)。字符串連接很昂貴,尤其是在這種規(guī)模下。讓我們使用一個(gè)字符串?dāng)?shù)組并將它們連接到最后。
var a = [];
for (var i=0; i<10000; i++) {
a.push("<div class=\"test-div\"></div>");
}
$("BODY").append(a.join(""));
63.885ms。這可能不足以改善壓力,但由于它更快,我們會(huì)保留它。
讓我們測(cè)量一下使用 jQuery 附加字符串的成本。正如我們?cè)谏弦徊街辛私獾降?,使用?JavaScript 可能會(huì)更快。
var a = [];
for (var i=0; i<10000; i++) {
a.push("<div class=\"test-div\"></div>");
}
document.body.innerHTML = a.join("");
這里的主要區(qū)別是我們沒(méi)有使用 jQuery.append
。相反,我們將 body
上的 innerHTML
屬性設(shè)置為我們的 HTML 字符串。代碼運(yùn)行需要22.908ms,因此改進(jìn)是顯而易見(jiàn)的。但是我們?nèi)匀粵](méi)有上一步的純 JavaScript 版本那么快。
4、結(jié)論
總之,最快的代碼是純 JavaScript 版本:
var c = document.createDocumentFragment();
for (var i=0; i<10000; i++) {
var e = document.createElement("div");
e.className = "test-div";
c.appendChild(e);
}
document.body.appendChild(c);
基于這些實(shí)驗(yàn),有兩個(gè)主要結(jié)論。
首先,要注意哪些操作會(huì)導(dǎo)致回流。這是一個(gè)昂貴的過(guò)程,應(yīng)該盡可能減少。
其次,要注意使用 jQuery 的成本。對(duì)于小規(guī)模的操作,jQuery 的便利性往往會(huì)超過(guò)性能成本。但是在大范圍內(nèi),除非確實(shí)需要,否則應(yīng)該避免使用 jQuery。
相關(guān)文章