JavaScript 性能優(yōu)化-內(nèi)存管理秀撇、V8(JavaScript 執(zhí)行引擎)超棺、監(jiān)控內(nèi)存、代碼優(yōu)化

Study Notes

本博主會持續(xù)更新各種前端的技術(shù)捌袜,如果各位道友喜歡说搅,可以關(guān)注、收藏虏等、點贊下本博主的文章弄唧。

JavaScript 內(nèi)存管理

簡介

像 C 語言這樣的底層語言一般都有底層的內(nèi)存管理接口,比如 malloc()和 free()霍衫。相反候引,JavaScript 是在創(chuàng)建變量(對象,字符串等)時自動進(jìn)行了分配內(nèi)存敦跌,并且在不使用它們時“自動”釋放澄干。 釋放的過程稱為垃圾回收。這個“自動”是混亂的根源柠傍,并讓 JavaScript(和其他高級語言)開發(fā)者錯誤的感覺他們可以不關(guān)心內(nèi)存管理麸俘。

內(nèi)存生命周期

不管什么程序語言,內(nèi)存生命周期基本是一致的:

  • 分配你所需要的內(nèi)存
  • 使用分配到的內(nèi)存(讀惧笛、寫)
  • 不需要時將其釋放\歸還

所有語言第二部分都是明確的从媚。第一和第三部分在底層語言中是明確的,但在像 JavaScript 這些高級語言中患整,大部分都是隱含的拜效。

JavaScript 的內(nèi)存分配

值的初始化

為了不讓程序員費心分配內(nèi)存,JavaScript 在定義變量時就完成了內(nèi)存分配各谚。

const n = 123; // 給數(shù)值變量分配內(nèi)存
const s = 'heath'; // 給字符串分配內(nèi)存

const o = {
  a: 1,
  b: null,
}; // 給對象及其包含的值分配內(nèi)存

// 給數(shù)組及其包含的值分配內(nèi)存(就像對象一樣)
const a = [1, null, 'abra'];

function f(a) {
  return a + 2;
} // 給函數(shù)(可調(diào)用的對象)分配內(nèi)存

// 函數(shù)表達(dá)式也能分配一個對象
someElement.addEventListener(
  'click',
  function () {
    someElement.style.backgroundColor = 'blue';
  },
  false,
);

通過函數(shù)調(diào)用分配內(nèi)存

有些函數(shù)調(diào)用結(jié)果是分配對象內(nèi)存:

const d = new Date(); // 分配一個 Date 對象

const e = document.createElement('div'); // 分配一個 DOM 元素

有些方法分配新變量或者新對象:

const s = 'heath';
const s2 = s.substr(0, 3); // s2 是一個新的字符串
// 因為字符串是不變量紧憾,
// JavaScript 可能決定不分配內(nèi)存,
// 只是存儲了 [0-3] 的范圍昌渤。

const a = ['heath heath', 'nan nan'];
const a2 = ['generation', 'nan nan'];
const a3 = a.concat(a2);
// 新數(shù)組有四個元素赴穗,是 a 連接 a2 的結(jié)果

使用值

使用值的過程實際上是對分配內(nèi)存進(jìn)行讀取與寫入的操作。讀取與寫入可能是寫入一個變量或者一個對象的屬性值膀息,甚至傳遞函數(shù)的參數(shù)望抽。

當(dāng)內(nèi)存不再需要使用時釋放

大多數(shù)內(nèi)存管理的問題都在這個階段。在這里最艱難的任務(wù)是找到“哪些被分配的內(nèi)存確實已經(jīng)不再需要了”履婉。它往往要求開發(fā)人員來確定在程序中哪一塊內(nèi)存不再需要并且釋放它煤篙。

高級語言解釋器嵌入了“垃圾回收器”,它的主要工作是跟蹤內(nèi)存的分配和使用毁腿,以便當(dāng)分配的內(nèi)存不再使用時辑奈,自動釋放它苛茂。這只能是一個近似的過程,因為要知道是否仍然需要某塊內(nèi)存是無法判定的(無法通過某種算法解決)鸠窗。

垃圾回收(Garbage collection)

如上文所述自動尋找是否一些內(nèi)存“不再需要”的問題是無法判定的妓羊。因此,垃圾回收實現(xiàn)只能有限制的解決一般問題稍计。本節(jié)將解釋必要的概念躁绸,了解主要的垃圾回收算法和它們的局限性。

引用

垃圾回收算法主要依賴于引用的概念臣嚣。在內(nèi)存管理的環(huán)境中净刮,一個對象如果有訪問另一個對象的權(quán)限(隱式或者顯式),叫做一個對象引用另一個對象硅则。例如淹父,一個 Javascript 對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。

在這里怎虫,“對象”的概念不僅特指 JavaScript 對象暑认,還包括函數(shù)作用域(或者全局詞法作用域)。

可達(dá)對象

  • 可以訪問到的對象就是可達(dá)對象(引用大审、作用域鏈)
  • 可達(dá)的標(biāo)準(zhǔn)就是從根出發(fā)是否能夠被找到
  • JavaScript 中的根就是可以理解為是全局變量對象

引用計數(shù)垃圾收集

這是最初級的垃圾收集算法蘸际。此算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用)徒扶,對象將被垃圾回收機(jī)制回收捡鱼。

實現(xiàn)原理

每個對象在創(chuàng)建的時候,就給這個對象綁定一個計數(shù)器酷愧。每當(dāng)有一個引用指向該對象時,計數(shù)器加一缠诅;每當(dāng)有一個指向它的引用被刪除時溶浴,計數(shù)器減一。這樣管引,當(dāng)沒有引用指向該對象時士败,該對象死亡,計數(shù)器為 0褥伴,這時就應(yīng)該對這個對象進(jìn)行垃圾回收操作谅将。

let o = {
  a: {
    b: 2,
  },
};
// 兩個對象被創(chuàng)建,一個作為另一個的屬性被引用重慢,另一個被分配給變量o
// 很顯然饥臂,沒有一個可以被垃圾收集

let o2 = o; // o2變量是第二個對“這個對象”的引用

o = 1; // 現(xiàn)在,“這個對象”只有一個o2變量的引用了似踱,“這個對象”的原始引用o已經(jīng)沒有

let oa = o2.a; // 引用“這個對象”的a屬性
// 現(xiàn)在隅熙,“這個對象”有兩個引用了稽煤,一個是o2,一個是oa

o2 = 'yo'; // 雖然最初的對象現(xiàn)在已經(jīng)是零引用了囚戚,可以被垃圾回收了
// 但是它的屬性a的對象還在被oa引用酵熙,所以還不能回收

oa = null; // a屬性的那個對象現(xiàn)在也是零引用了
// 它可以被垃圾回收了

限制:循環(huán)引用

該算法有個限制:無法處理循環(huán)引用的事例。在下面的例子中驰坊,兩個對象被創(chuàng)建匾二,并互相引用,形成了一個循環(huán)拳芙。它們被調(diào)用之后會離開函數(shù)作用域察藐,所以它們已經(jīng)沒有用了,可以被回收了态鳖。然而转培,引用計數(shù)算法考慮到它們互相都有至少一次引用,所以它們不會被回收浆竭。

function f() {
  let o = {};
  let o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return 'heath';
}

f();

實際例子

IE 6, 7 使用引用計數(shù)方式對 DOM 對象進(jìn)行垃圾回收浸须。該方式常常造成對象被循環(huán)引用時內(nèi)存發(fā)生泄漏:

let div;
window.onload = function () {
  div = document.getElementById('myDivElement');
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join('*');
};

在上面的例子里,myDivElement 這個 DOM 元素里的 circularReference 屬性引用了 myDivElement邦泄,造成了循環(huán)引用删窒。如果該屬性沒有顯示移除或者設(shè)為 null,引用計數(shù)式垃圾收集器將總是且至少有一個引用顺囊,并將一直保持在內(nèi)存里的 DOM 元素肌索,即使其從 DOM 樹中刪去了。如果這個 DOM 元素?fù)碛写罅康臄?shù)據(jù) (如上的 lotsOfData 屬性)特碳,而這個數(shù)據(jù)占用的內(nèi)存將永遠(yuǎn)不會被釋放诚亚。

引用計數(shù)優(yōu)缺點

優(yōu)點

  • 發(fā)現(xiàn)垃圾時,立即回收
  • 最大限度減少程序暫停

缺點

  • 無法回收循環(huán)引用的對象
  • 時間開銷大

標(biāo)記-清除算法

這個算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”午乓。

這個算法假定設(shè)置一個叫做根(root)的對象(在 Javascript 里站宗,根是全局對象)。垃圾回收器將定期從根開始益愈,找所有從根開始引用的對象梢灭,然后找這些對象引用的對象……從根開始,垃圾回收器將找到所有可以獲得的對象和收集所有不能獲得的對象蒸其。

這個算法比前一個要好敏释,因為“有零引用的對象”總是不可獲得的,但是相反卻不一定摸袁,參考“循環(huán)引用”钥顽。

從 2012 年起,所有現(xiàn)代瀏覽器都使用了標(biāo)記-清除垃圾回收算法靠汁。所有對 JavaScript 垃圾回收算法的改進(jìn)都是基于標(biāo)記-清除算法的改進(jìn)耳鸯,并沒有改進(jìn)標(biāo)記-清除算法本身和它對“對象是否不再需要”的簡化定義湿蛔。

實現(xiàn)原理

  • 核心思想:分標(biāo)記和清除二個階段完成
  • 遍歷所有對象并標(biāo)記活動對象
  • 遍歷所有對象清除沒有標(biāo)記的對象
  • 回收相應(yīng)的空間
note

循環(huán)引用不再是問題了

在上面的示例中,函數(shù)調(diào)用返回之后县爬,兩個對象從全局對象出發(fā)無法獲取阳啥。因此,他們將會被垃圾回收器回收财喳。第二個示例同樣察迟,一旦 div 和其事件處理無法從根獲取到,他們將會被垃圾回收器回收耳高。

限制: 那些無法從根對象查詢到的對象都將被清除

盡管這是一個限制扎瓶,但實踐中我們很少會碰到類似的情況,所以開發(fā)者不太會去關(guān)心垃圾回收機(jī)制泌枪。

標(biāo)記-清除算法缺點

容易產(chǎn)生內(nèi)存碎片化空間

  • 標(biāo)記算法并未在清除未標(biāo)記對象的時候概荷,進(jìn)行整理,所以在清除標(biāo)記后碌燕,會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片误证。

  • 當(dāng)分配的內(nèi)存大于現(xiàn)有連續(xù)的內(nèi)存碎片,則會提前觸發(fā)新一輪的垃圾回收動作修壕;

  • 當(dāng)分配的內(nèi)存小于現(xiàn)有連續(xù)的內(nèi)存碎片愈捅,則可能會造成浪費。

note

標(biāo)記整理算法

實現(xiàn)原理

  • 標(biāo)記整理可以看做是標(biāo)記清除的增強(qiáng)
  • 標(biāo)記階段的操作和標(biāo)記清除一致
  • 清除階段會先執(zhí)行整理慈鸠,移動對象位置
note

V8(JavaScript 執(zhí)行引擎)

  • V8 引擎是一個 JavaScript 引擎實現(xiàn)蓝谨,最初由一些語言方面專家設(shè)計,后被谷歌收購青团,隨后谷歌對其進(jìn)行了開源譬巫。
  • V8 使用 C++開發(fā),在運行 JavaScript 之前督笆,相比其它的 JavaScript 的引擎轉(zhuǎn)換成字節(jié)碼或解釋執(zhí)行芦昔,V8 將其編譯成原生機(jī)器碼(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如內(nèi)聯(lián)緩存(inline caching)等方法來提高性能胖腾。
  • 有了這些功能,JavaScript 程序在 V8 引擎下的運行速度媲美二進(jìn)制程序瘪松。
  • V8 支持眾多操作系統(tǒng)咸作,如 windows、linux宵睦、android 等记罚,也支持其他硬件架構(gòu),如 IA32,X64,ARM 等壳嚎,具有很好的可移植和跨平臺特性桐智。

內(nèi)存管理

V8 內(nèi)存限制

限制大小

64 位為 1.4GB末早,32 位為 0.7GB

限制原因

V8 之所以限制了內(nèi)存的大小,表面上的原因是 V8 最初是作為瀏覽器的 JavaScript 引擎而設(shè)計说庭,不太可能遇到大量內(nèi)存的場景然磷,而深層次的原因則是由于 V8 的垃圾回收機(jī)制的限制。由于 V8 需要保證 JavaScript 應(yīng)用邏輯與垃圾回收器所看到的不一樣刊驴,V8 在執(zhí)行垃圾回收時會阻塞 JavaScript 應(yīng)用邏輯姿搜,直到垃圾回收結(jié)束再重新執(zhí)行 JavaScript 應(yīng)用邏輯,這種行為被稱為“全停頓”(stop-the-world)捆憎。若 V8 的堆內(nèi)存為 1.5GB舅柜,V8 做一次小的垃圾回收需要 50ms 以上,做一次非增量式的垃圾回收甚至要 1 秒以上躲惰。這樣瀏覽器將在 1s 內(nèi)失去對用戶的響應(yīng)致份,造成假死現(xiàn)象。如果有動畫效果的話础拨,動畫的展現(xiàn)也將顯著受到影響氮块。

V8 垃圾回收策略

  • 采用分代回收的思想
  • 內(nèi)存分為新生代、老生代
  • 針對新太伊、老生代采用不同算法來提升垃圾回收的效率

新生代的對象為存活時間較短的對象雇锡,老生代中的對象為存活時間較長或常駐內(nèi)存的對象。

V8 新生代僚焦、老生代內(nèi)存大小

V8 引擎的新生代內(nèi)存大小 32MB(64 位)锰提、16MB(32 位),老生代內(nèi)存大小為 1400MB(64 位)芳悲、700MB( 32 位)立肘。

新生代對象回收實現(xiàn)

  • 回收過程采用復(fù)制算法+標(biāo)記整理
  • 新生代內(nèi)存區(qū)被等分為兩個空間
  • 使用空間為 From,空閑空間為 To
  • 標(biāo)記整理后將活動對象拷貝至 To
  • From 和 To 交換空間完成釋放
note

晉升

將新生代對象移到老生代

晉升條件

  • 一輪 GC 還存活的新生代需要晉升
  • 對象從 From 空間復(fù)制到 To 空間時名扛,如果 To 空間已經(jīng)被使用了超過 25%谅年,那么這個對象直接被復(fù)制到老生代

老生代對象回收實現(xiàn)

  • 主要采取標(biāo)記清除、標(biāo)記整理肮韧、增量標(biāo)記算法
  • 首先使用標(biāo)記清除完成垃圾空間的回收
  • 采用標(biāo)記整理進(jìn)行空間優(yōu)化
  • 采用增量標(biāo)記進(jìn)行效率優(yōu)化

細(xì)節(jié)對比

新生代區(qū)域融蹂,采用復(fù)制算法, 因此其每時每刻內(nèi)部都有空閑空間的存在(為了完成 From 到 To 的對象復(fù)制)弄企,但是新生代區(qū)域空間較小(32M)且被一分為二超燃,所以這種空間上的浪費也是比較微不足道的。

老生代因其空間較大(1.4G),如果同樣采用一分為二的做法則對空間大小是比較浪費拘领,且老生代空間較大意乓,存放對對象也較多,如果進(jìn)行復(fù)制算法约素,則其消耗對時間也會更大届良。也就是是否使用復(fù)制算法來進(jìn)行垃圾回收笆凌,是一個時間 T 關(guān)于內(nèi)存大小的關(guān)系,當(dāng)內(nèi)存大小較小時士葫,使用復(fù)制算法消耗的時間是比較短的乞而,而當(dāng)內(nèi)存較大時,采用復(fù)制算法對時間對消耗也就更大为障。

V8 的優(yōu)化

增量標(biāo)記

由于全停頓會造成了瀏覽器一段時間無響應(yīng)晦闰,所以 V8 使用了一種增量標(biāo)記的方式,將完整的標(biāo)記拆分成很多部分鳍怨,每做完一部分就停下來呻右,讓 JS 的應(yīng)用邏輯執(zhí)行一會,這樣垃圾回收與應(yīng)用邏輯交替完成鞋喇。經(jīng)過增量標(biāo)記的改進(jìn)后声滥,垃圾回收的最大停頓時間可以減少到原來的 1/6 左右

note

惰性清理

由于標(biāo)記完成后,所有的對象都已經(jīng)被標(biāo)記侦香,不是死對象就是活對象落塑,堆上多少空間格局已經(jīng)確定。我們可以不必著急釋放那些死對象所占用的空間罐韩,而延遲清理過程的執(zhí)行憾赁。垃圾回收器可以根據(jù)需要逐一清理死對象所占用的內(nèi)存空間

其他

V8 后續(xù)還引入了增量式整理(incremental compaction),以及并行標(biāo)記和并行清理散吵,通過并行利用多核 CPU 來提升垃圾回收的性能

監(jiān)控內(nèi)存

內(nèi)存問題的外在表現(xiàn)

  • 頁面出現(xiàn)延遲加載或經(jīng)常性暫停: 可能存在頻繁當(dāng) GC 操作,存在一些代碼瞬間吃滿了內(nèi)存龙考。
  • 頁面出現(xiàn)持續(xù)性的糟糕性能: 程序為了達(dá)到最優(yōu)的運行速度,向內(nèi)存申請了一片較大的內(nèi)存空間矾睦,但空間大小超過了設(shè)備所能提供的大小晦款。
  • 頁面使用隨著時間延長越來越卡: 可能存在內(nèi)存泄漏。

界定內(nèi)存問題的標(biāo)準(zhǔn)

  • 內(nèi)存泄漏:內(nèi)存使用持續(xù)升高
  • 內(nèi)存膨脹:在多數(shù)設(shè)備上都存在性能問題
  • 頻繁垃圾回收:通過內(nèi)存變化時序圖進(jìn)行分析

監(jiān)控內(nèi)存方式

任務(wù)管理器

這里以 Google 瀏覽器為例,使用 Shift + Esc 喚起 Google 瀏覽器自帶的任務(wù)管理器

  • Memory(內(nèi)存) 列表示原生內(nèi)存枚冗。DOM 節(jié)點存儲在原生內(nèi)存中缓溅。 如果此值正在增大,則說明正在創(chuàng)建 DOM 節(jié)點赁温。
  • JavaScript Memory(JavaScript 內(nèi)存) 列表示 JS 堆坛怪。此列包含兩個值。 您感興趣的值是實時數(shù)字(括號中的數(shù)字)股囊。 實時數(shù)字表示您的頁面上的可到達(dá)對象正在使用的內(nèi)存量袜匿。 如果此數(shù)字在增大,要么是正在創(chuàng)建新對象毁涉,要么是現(xiàn)有對象正在增長沉帮。

模擬內(nèi)存泄漏

在任務(wù)管理器里可以看到 JavaScript 內(nèi)存持續(xù)上升

document.body.innerHTML = `<button id="add">add</button>`;
document.getElementById('add').addEventListener('click', function (e) {
  simulateMemoryLeak();
});
let result = [];
function simulateMemoryLeak() {
  setInterval(function () {
    result.push(new Array(1000000).join('x'));
    document.body.innerHTML = result;
  }, 100);
}

Timeline 記錄內(nèi)存

這里以 Google 瀏覽器為例,使用 F12 開啟調(diào)式锈死,選擇 Performance贫堰,點擊 record(錄制)穆壕,進(jìn)行頁面操作,點擊 stop 結(jié)束錄制之后其屏,開啟內(nèi)存勾選喇勋,拖動截圖到指定時間段查看發(fā)生內(nèi)存問題時候到頁面展示,并定位問題偎行。同時可以查看對應(yīng)出現(xiàn)紅點到執(zhí)行腳本川背,定位問題代碼。

note

利用瀏覽器內(nèi)存模塊蛤袒,查找分離 dom

這里以 Google 瀏覽器為例,在頁面上進(jìn)行相關(guān)操作后熄云,使用 F12 開啟調(diào)式,選擇 Memory妙真,點擊 Take snapshot(拍照)缴允,在快照中查找 Detached HTMLElement,回到代碼中查找對應(yīng)的分離 dom 存在的代碼,在相關(guān)操作代碼之后珍德,對分離 dom 進(jìn)行釋放练般,防止內(nèi)存泄漏。

只有頁面的 DOM 樹或 JavaScript 代碼不再引用 DOM 節(jié)點時锈候,DOM 節(jié)點才會被作為垃圾進(jìn)行回收薄料。 如果某個節(jié)點已從 DOM 樹移除,但某些 JavaScript 仍然引用它泵琳,我們稱此節(jié)點為“已分離”摄职。已分離的 DOM 節(jié)點是內(nèi)存泄漏的常見原因。

模擬已分離 DOM 節(jié)點

document.body.innerHTML = `<button id="add">add</button>`;
document.getElementById('add').addEventListener('click', function (e) {
  create();
});
let detachedTree;
function create() {
  let ul = document.createElement('ul');
  for (let i = 0; i < 10; i++) {
    let li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}
note

如何確定頻繁對垃圾回收

  • GC 工作時虑稼,程序是暫停的琳钉,頻繁/過長的 GC 會導(dǎo)致程序假死,用戶會感知到卡頓蛛倦。
  • 查看 Timeline 中是否存在內(nèi)存走向在短時間內(nèi)頻繁上升下降的區(qū)域歌懒。瀏覽器任務(wù)管理器是否頻繁的增加減少。

代碼優(yōu)化

jsPerf(JavaScript 性能測試)

基于 Benchmark.js

慎用全局變量

  • 全局變量定義在全局執(zhí)行的上下文,是所有作用域鏈的頂端
  • 全局執(zhí)行上下文一直存在于上下文執(zhí)行棧溯壶,直到程序退出
  • 如果某個局部作用域出現(xiàn)了同名變量則會屏蔽或者污染全局作用域
  • 全局變量的執(zhí)行速度及皂,訪問速度要低于局部變量,因此對于一些需要經(jīng)常訪問的全局變量可以在局部作用域中進(jìn)行緩存
note

上圖可以看出且改,test2 的性能要比 test1 的性能要好验烧,從而得知,全局變量的執(zhí)行速度又跛,訪問速度要低于局部變量

避免全局查找

note

上圖可以看出碍拆,test2 的性能要比 test1 的性能要好,從而得知,緩存全局變量后使用可以提升性能

通過原型對象添加附加方法提高性能

note

上圖可以看出感混,test2 的性能要比 test1 的性能要好端幼,從而得知,通過原型對象添加方法與直接在對象上添加成員方法相比弧满,原型對象上的屬性訪問速度較快婆跑。

避開閉包陷阱

閉包特點

  • 外部具有指向內(nèi)部的引用
  • 在“外”部作用域訪問“內(nèi)”部作用域的數(shù)據(jù)
function foo() {
  let name = 'heath';
  function fn() {
    console.log(name);
  }
  return fn;
}
let a = foo();
a();

閉包使用不當(dāng)很容易出現(xiàn)內(nèi)存泄漏

function f5() {
  // el 引用了全局變量document,假設(shè)btn節(jié)點被刪除后庭呜,因為這里被引用著滑进,所以這里不會被垃圾回收,導(dǎo)致內(nèi)存泄漏
  let el = document.getElementById('btn');
  el.onclick = function (e) {
    console.log(e.id);
  };
}
f5();

function f6() {
  // el 引用了全局變量document募谎,假設(shè)btn節(jié)點被刪除后扶关,因為這里被引用著,所以這里不會被垃圾回收数冬,導(dǎo)致內(nèi)存泄漏
  let el = document.getElementById('btn');
  el.onclick = function (e) {
    console.log(e.id);
  };
  el = null; // 我們這里手動將el內(nèi)存釋放驮审,從而當(dāng)btn節(jié)點被刪除后,可以被垃圾回收
}
f6();

避免屬性訪問方法使用

JavaScript 中的面向?qū)ο?/p>

  • JS 不需屬性的訪問方法吉执,所有屬性都是外部可見的
  • 使用屬性訪問方法只會增加一層重定義疯淫,沒有訪問的控制力
note

上圖可以看出,test2 的性能要比 test1 的性能要好不少戳玫,從而得知熙掺,直接訪問屬性,會比通過方法訪問屬性速度來的快咕宿。

遍歷速度

note

上圖可以看出币绩,loop 遍歷速度 forEach > 優(yōu)化 for > for of > for > for in

dom 節(jié)點操作

note

上圖可以看出,節(jié)點克隆(cloneNode)生成節(jié)點速度要快于創(chuàng)建節(jié)點府阀。

采用字面量替換 New 操作

note

上圖可以看出缆镣,字面量聲明的數(shù)據(jù)生成速度要快于單獨屬性賦值行為生成的數(shù)據(jù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末试浙,一起剝皮案震驚了整個濱河市董瞻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌田巴,老刑警劉巖钠糊,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壹哺,居然都是意外死亡抄伍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門管宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來截珍,“玉大人攀甚,你說我怎么就攤上這事「诤恚” “怎么了云稚?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沈堡。 經(jīng)常有香客問我,道長燕雁,這世上最難降的妖魔是什么诞丽? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮拐格,結(jié)果婚禮上僧免,老公的妹妹穿的比我還像新娘。我一直安慰自己捏浊,他們只是感情好懂衩,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著金踪,像睡著了一般浊洞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胡岔,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天法希,我揣著相機(jī)與錄音,去河邊找鬼靶瘸。 笑死苫亦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怨咪。 我是一名探鬼主播屋剑,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诗眨!你這毒婦竟也來了唉匾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤匠楚,失蹤者是張志新(化名)和其女友劉穎肄鸽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體油啤,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡典徘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了益咬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逮诲。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡帜平,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梅鹦,到底是詐尸還是另有隱情裆甩,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布齐唆,位于F島的核電站嗤栓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箍邮。R本人自食惡果不足惜茉帅,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锭弊。 院中可真熱鬧堪澎,春花似錦、人聲如沸味滞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剑鞍。三九已至昨凡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚁署,已是汗流浹背土匀。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留形用,地道東北人就轧。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像田度,于是被迫代替她去往敵國和親妒御。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348