編寫高效能JavaScript(譯),高效能javascript

來源:互聯網
上載者:User

編寫高效能JavaScript(譯),高效能javascript

譯者按:本人第一次翻譯外文,言語難免有些晦澀,但盡量表達了作者的原意,未經過多的潤色,歡迎批評指正。另本文篇幅較長、資訊量大,可能難以消化,歡迎留言探討細節問題。本文主要關注V8的效能最佳化,部分內容並不適用於所有JS引擎。最後,轉載請註明出處: )

========================譯文分割線===========================

很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是專門為需要快速執行的大型JavaScript應用所設計的。如果你是一個開發人員,並且關心記憶體使用量情況與頁面效能,你應該瞭解使用者瀏覽器中的JavaScript引擎是如何運作的。無論是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,這樣做可以協助你更好地最佳化你的應用程式。這並不是說應該專門為某一瀏覽器或引擎做最佳化,千萬別這麼做。

但是,你應該問自己幾個問題:

  • 在My Code裡,是否可以使代碼更高效一些
  • 主流的JavaScript引擎都做了哪些最佳化
  • 什麼是引擎無法最佳化的,記憶體回收行程(GC)是否能回收我所期望的東西

載入快速的網站就像是一輛快速的跑車,需要用到特別定製的零件. 圖片來源: dHybridcars.

編寫高效能代碼時有一些常見的陷阱,在這篇文章中,我們將展示一些經過驗證的、更好的編寫代碼方式。

那麼,JavaScript在V8裡是如何工作的?

如果你對JS引擎沒有較深的瞭解,開發一個大型Web應用也沒啥問題,就好比會開車的人也只是看過引擎蓋而沒有看過車蓋內的引擎一樣。鑒於Chrome是我的瀏覽器首選,所以談一下它的JavaScript引擎。V8是由以下幾個核心部分組成:

  • 一個基本的編譯器,它會在代碼執行前解析JavaScript代碼並產生本地機器碼,而不是執行位元組碼或簡單地解釋它。這些代碼最開始並不是高度最佳化的。
  • V8將對象構建為物件模型。在JavaScript中對象表現為關聯陣列,但是在V8中對象被看作是隱藏的類,一個為了最佳化查詢的內部類型系統。
  • 運行時分析器監視正在啟動並執行系統,並標識了“hot”的函數(例如花費很長時間啟動並執行代碼)。
  • 最佳化編譯器重新編譯和最佳化那些被運行時分析器標識為“hot”的代碼,並進行“內聯”等最佳化(例如用被調用者的主體替換函數調用的位置)。
  • V8支援去最佳化,這意味著最佳化編譯器如果發現對於代碼最佳化的假設過於樂觀,它會捨棄最佳化過的代碼。
  • V8有個垃圾收集器,瞭解它是如何工作的和最佳化JavaScript一樣重要。
記憶體回收

記憶體回收是記憶體管理的一種形式,其實就是一個收集器的概念,嘗試回收不再被使用的對象所佔用的記憶體。在JavaScript這種記憶體回收語言中,應用程式中仍在被引用的對象不會被清除。

手動消除對象引用在大多數情況下是沒有必要的。通過簡單地把變數放在需要它們的地方(理想情況下,儘可能是局部範圍,即它們被使用的函數裡而不是函數外層),一切將運作地很好。

記憶體回收行程嘗試回收記憶體. 圖片來源: Valtteri Mäki.

在JavaScript中,是不可能強制進行記憶體回收的。你不應該這麼做,因為垃圾收集過程是由運行時控制的,它知道什麼是最好的清理時機。

“消除引用”的誤解

網上有許多關於JavaScript記憶體回收的討論都談到delete這個關鍵字,雖然它可以被用來刪除對象(map)中的屬性(key),但有部分開發人員認為它可以用來強制“消除引用”。建議儘可能避免使用delete,在下面的例子中delete o.x 的弊大於利,因為它改變了o的隱藏類,並使它成為一個"慢對象"。

var o = { x: 1 }; delete o.x; // true o.x; // undefined

你會很容易地在流行的JS庫中找到引用刪除——這是具有語言目的性的。這裡需要注意的是避免在運行時修改”hot”對象的結構。JavaScript引擎可以檢測出這種“hot”的對象,並嘗試對其進行最佳化。如果對象在生命週期中其結構沒有較大的改變,引擎將會更容易最佳化對象,而delete操作實際上會觸發這種較大的結構改變,因此不利於引擎的最佳化。

對於null是如何工作也是有誤解的。將一個對象引用設定為null,並沒有使對象變“空”,只是將它的引用設定為空白而已。使用o.x= null比使用delete會更好些,但可能也不是很必要。

var o = { x: 1 }; o = null;o; // nullo.x // TypeError

如果此引用是當前對象的最後引用,那麼該對象將被作為記憶體回收。如果此引用不是當前對象的最後引用,則該對象是可訪問的且不會被記憶體回收。

另外需要注意的是,全域變數在頁面的生命週期裡是不被記憶體回收行程清理的。無論頁面開啟多久,JavaScript運行時全域對象範圍中的變數會一直存在。

var myGlobalNamespace = {};

全域對象只會在重新整理頁面、導航到其他頁面、關閉標籤頁或退出瀏覽器時才會被清理。函數範圍的變數將在超出範圍時被清理,即退出函數時,已經沒有任何引用,這樣的變數就被清理了。

經驗法則

為了使記憶體回收行程儘早收集儘可能多的對象,不要hold著不再使用的對象。這裡有幾件事需要記住:

  • 正如前面提到的,在合適的範圍內使用變數是手動消除引用的更好選擇。即一個變數只在一個函數範圍中使用,就不要在全域範圍聲明它。這意味著更乾淨省心的代碼。
  • 確保解除綁定那些不再需要的事件監聽器,尤其是那些即將被銷毀的DOM對象所綁定的事件監聽器。
  • 如果使用的資料緩衝在本地,確保清理一下緩衝或使用老化機制,以避免大量不被重用的資料被儲存。
函數

接下來,我們談談函數。正如我們已經說過,垃圾收集的工作原理,是通過回收不再是訪問的記憶體塊(對象)。為了更好地說明這一點,這裡有一些例子。

function foo() { var bar = new LargeObject(); bar.someCall();}

當foo返回時,bar指向的對象將會被垃圾收集器自動回收,因為它已沒有任何存在的引用了。

對比一下:

function foo() { var bar = new LargeObject(); bar.someCall(); return bar;}// somewhere elsevar b = foo();

現在我們有一個引用指向bar對象,這樣bar對象的生存周期就從foo的調用一直持續到調用者指定別的變數b(或b超出範圍)。

閉包(CLOSURES)

當你看到一個函數,返回一個內建函式,該內建函式將獲得範圍外的訪問權,即使在外部函數執行之後。這是一個基本的閉包 —— 可以在特定的上下文中設定的變數的運算式。例如:

function sum (x) { function sumIt(y) {  return x + y; }; return sumIt;}// Usagevar sumA = sum(4);var sumB = sumA(3);console.log(sumB); // Returns 7

在sum調用上下文中產生的函數對象(sumIt)是無法被回收的,它被全域變數(sumA)所引用,並且可以通過sumA(n)調用。

讓我們來看看另外一個例子,這裡我們可以訪問變數largeStr嗎?

var a = function () { var largeStr = new Array(1000000).join('x'); return function () {  return largeStr; };}();

是的,我們可以通過a()訪問largeStr,所以它沒有被回收。下面這個呢?

var a = function () { var smallStr = 'x'; var largeStr = new Array(1000000).join('x'); return function (n) {  return smallStr; };}();

我們不能再訪問largeStr了,它已經是記憶體回收候選人了。【譯者註:因為largeStr已不存在外部參考了】

定時器

最糟的記憶體流失地方之一是在迴圈中,或者在setTimeout()/ setInterval()中,但這是相當常見的。思考下面的例子:

var myObj = { callMeMaybe: function () {  var myRef = this;  var val = setTimeout(function () {    console.log('Time is running out!');    myRef.callMeMaybe();  }, 1000); }};

如果我們運行myObj.callMeMaybe();來啟動定時器,可以看到控制台每秒列印出“Time is running out!”。如果接著運行myObj = null,定時器依舊處於啟用狀態。為了能夠持續執行,閉包將myObj傳遞給setTimeout,這樣myObj是無法被回收的。相反,它引用到myObj的因為它捕獲了myRef。這跟我們為了保持引用將閉包傳給其他的函數是一樣的。

同樣值得牢記的是,setTimeout/setInterval調用(如函數)中的引用,將需要執行和完成,才可以被垃圾收集。

當心效能陷阱

永遠不要最佳化代碼,直到你真正需要。現在經常可以看到一些基準測試,顯示N比M在V8中更為最佳化,但是在模組代碼或應用中測試一下會發現,這些最佳化真正的效果比你期望的要小的多。

做的過多還不如什麼都不做. 圖片來源: Tim Sheerman-Chase.

比如我們想要建立這樣一個模組:

  • 需要一個本地的資料來源包含數字ID
  • 繪製包含這些資料的表格
  • 添加事件處理常式,當使用者點擊的任何儲存格時切換儲存格的css class

這個問題有幾個不同的因素,雖然也很容易解決。我們如何儲存資料,如何高效地繪製表格並且append到DOM中,如何更優地處理表格事件?

面對這些問題最開始(天真)的做法是使用Object Storage Service資料並放入數組中,使用jQuery遍曆資料繪製表格並append到DOM中,最後使用事件綁定我們期望地點擊行為。

注意:這不是你應該做的

var moduleA = function () { return {  data: dataArrayObject,  init: function () {   this.addTable();   this.addEvents();  },  addTable: function () {   for (var i = 0; i < rows; i++) {    $tr = $('<tr></tr>');    for (var j = 0; j < this.data.length; j++) {     $tr.append('<td>' + this.data[j]['id'] + '</td>');    }    $tr.appendTo($tbody);   }  },  addEvents: function () {   $('table td').on('click', function () {    $(this).toggleClass('active');   });  } };}();

這段代碼簡單有效地完成了任務。

但在這種情況下,我們遍曆的資料只是本應該簡單地存放在數組中的數字型屬性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以這種方式)來產生表格是更優的選擇,當然,事件代理比單獨綁定每個td具有更高的效能。

要注意雖然jQuery在內部使用DocumentFragment,但是在我們的例子中,代碼在迴圈內調用append並且這些調用涉及到一些其他的小知識,因此在這裡起到的最佳化作用不大。希望這不會是一個痛點,但請務必進行基準測試,以確保自己代碼ok。

對於我們的例子,上述的做法帶來了(期望的)效能提升。事件代理對簡單的綁定是一種改進,可選的DocumentFragment也起到了助推作用。

var moduleD = function () { return {  data: dataArray,  init: function () {   this.addTable();   this.addEvents();  },  addTable: function () {   var td, tr;   var frag = document.createDocumentFragment();   var frag2 = document.createDocumentFragment();   for (var i = 0; i < rows; i++) {    tr = document.createElement('tr');    for (var j = 0; j < this.data.length; j++) {     td = document.createElement('td');     td.appendChild(document.createTextNode(this.data[j]));     frag2.appendChild(td);    }    tr.appendChild(frag2);    frag.appendChild(tr);   }   tbody.appendChild(frag);  },  addEvents: function () {   $('table').on('click', 'td', function () {    $(this).toggleClass('active');   });  } };}();

接下來看看其他提升效能的方式。你也許曾經在哪讀到過使用原型模式比模組模式更優,或聽說過使用JS模版架構效能更好。有時的確如此,不過使用它們其實是為了代碼更具可讀性。對了,還有先行編譯!讓我們看看在實踐中表現的如何?

moduleG = function () {};moduleG.prototype.data = dataArray;moduleG.prototype.init = function () { this.addTable(); this.addEvents();};moduleG.prototype.addTable = function () { var template = _.template($('#template').text()); var html = template({'data' : this.data}); $tbody.append(html);};moduleG.prototype.addEvents = function () { $('table').on('click', 'td', function () {  $(this).toggleClass('active'); });};var modG = new moduleG();

事實證明,在這種情況下的帶來的效能提升可以忽略不計。模板和原型的選擇並沒有真正提供更多的東西。也就是說,效能並不是開發人員使用它們的原因,給代碼帶來的可讀性、繼承模型和可維護性才是真正的原因。

更複雜的問題包括高效地在canvas上繪製圖片和操作帶或不帶類型數組的像素資料。

在將一些方法用在你自己的應用之前,一定要多瞭解這些方案的基準測試。也許有人還記得JS模版的shoot-off和隨後的擴充版。你要搞清楚基準測試不是存在於你看不到的那些虛擬應用,而是應該在你的實際代碼中去測試帶來的最佳化。

V8最佳化技巧

詳細介紹了每個V8引擎的最佳化點在本文討論範圍之外,當然這裡也有許多值得一提的技巧。記住這些技巧你就能減少那些效能低下的代碼了。

  • 特定模式可以使V8擺脫最佳化的困境,比如說try-catch。欲瞭解更多有關哪些函數能或不能進行最佳化,你可以在V8的指令碼工具d8中使用–trace-opt file.js命令。
  • 如果你關心速度,盡量使你的函數職責單一,即確保變數(包括屬性,數組,函數參數)只使用相同隱藏類包含的對象。舉個例子,別這麼幹:
    function add(x, y) {  return x+y;} add(1, 2); add('a','b'); add(my_custom_object, undefined);
  • 不要載入未初始化或已刪除的元素。如果這麼做也不會出現什麼錯誤,但是這樣會使速度變慢。
  • 不要使函數體過大,這樣會使得最佳化更加困難。

更多內容可以去看Daniel Clifford在Google I/O的分享 Breaking the JavaScript Speed Limit with V8。 Optimizing For V8 — A Series也非常值得一讀。

對象VS數組:我應該用哪個?
  • 如果你想儲存一串數字,或者一些相同類型的對象,使用一個數組。
  • 如果你語義上需要的是一堆的對象的屬性(不同類型的),使用一個對象和屬性。這在記憶體方面非常高效,速度也相當快。
  • 整數索引的元素,無論儲存在一個數組或對象中,都要比遍曆對象的屬性快得多。
  • 對象的屬性比較複雜:它們可以被setter們建立,具有不同的枚舉性和可寫性。數組中則不具有如此的定製性,而只存在有和無這兩種狀態。在引擎層面,這允許更多儲存結構方面的最佳化。特別是當數組中存在數字時,例如當你需要容器時,不用定義具有x,y,z屬性的類,而只用數組就可以了。

JavaScript中對象和數組之間只有一個的主要區別,那就是數組神奇的length屬性。如果你自己來維護這個屬性,那麼V8中對象和數組的速度是一樣快的。

使用對象時的技巧
  • 使用一個建構函式來建立對象。這將確保它建立的所有對象具有相同的隱藏類,並有助於避免更改這些類。作為一個額外的好處,它也略快於Object.create()
  • 你的應用中,對於使用不同類型的對象和其複雜度(在合理的範圍內:長原型鏈往往是有害的,呈現只有一個極少數屬性的對象比大對象會快一點)是有沒限制的。對於“hot”對象,盡量保持短原型鏈,並且少屬性。
對象複製

對於應用程式開發人員,對象複製是一個常見的問題。雖然各種基準測試可以證明V8對這個問題處理得很好,但仍要小心。複製大的東西通常是較慢的——不要這麼做。JS中的for..in迴圈尤其糟糕,因為它有著惡魔般的規範,並且無論是在哪個引擎中,都可能永遠不會比任何對象快。

當你一定要在關鍵效能代碼路徑上複製對象時,使用數組或一個自訂的“拷貝建構函式”功能明確地複製每個屬性。這可能是最快的方式:

function clone(original) { this.foo = original.foo; this.bar = original.bar;}var copy = new clone(original);
模組模式中緩衝函數

使用模組模式時緩衝函數,可能會導致效能方面的提升。參閱下面的例子,因為它總是建立成員函數的新副本,你看到的變化可能會比較慢。

另外請注意,使用這種方法明顯更優,不僅僅是依靠原型模式(經過jsPerf測試確認)。

使用模組模式或原型模式時的效能提升

這是一個原型模式與模組模式的效能對比測試:

 // Prototypal pattern Klass1 = function () {} Klass1.prototype.foo = function () {  log('foo'); } Klass1.prototype.bar = function () {  log('bar'); } // Module pattern Klass2 = function () {  var foo = function () {   log('foo');  },  bar = function () {   log('bar');  };  return {   foo: foo,   bar: bar  } } // Module pattern with cached functions var FooFunction = function () {  log('foo'); }; var BarFunction = function () {  log('bar'); }; Klass3 = function () {  return {   foo: FooFunction,   bar: BarFunction  } } // Iteration tests // Prototypal var i = 1000,  objs = []; while (i--) {  var o = new Klass1()  objs.push(new Klass1());  o.bar;  o.foo; } // Module pattern var i = 1000,  objs = []; while (i--) {  var o = Klass2()  objs.push(Klass2());  o.bar;  o.foo; } // Module pattern with cached functions var i = 1000,  objs = []; while (i--) {  var o = Klass3()  objs.push(Klass3());  o.bar;  o.foo; }// See the test for full details
使用數組時的技巧

接下來說說數組相關的技巧。在一般情況下,不要刪除數組元素,這樣將使數組過渡到較慢的內部表示。當索引變得稀疏,V8將會使元素轉為更慢的字典模式。

數組字面量

數組字面量非常有用,它可以暗示VM數組的大小和類型。它通常用在體積不大的數組中。

// Here V8 can see that you want a 4-element array containing numbers:var a = [1, 2, 3, 4];// Don't do this:a = []; // Here V8 knows nothing about the arrayfor(var i = 1; i <= 4; i++) {  a.push(i);}
儲存單一類型VS多類型

將混合類型(比如數字、字串、undefined、true/false)的資料存在數組中絕不是一個好想法。例如var arr = [1, “1”, undefined, true, “true”]

類型推斷的效能測試

正如我們所看到的結果,整數的數組是最快的。

稀疏數組與滿數組

當你使用稀疏數組時,要注意訪問元素將遠遠慢於滿數組。因為V8不會分配一整塊空間給只用到部分空間的數組。取而代之的是,它被管理在字典中,既節約了空間,但花費訪問的時間。

稀疏數組與滿數組的測試

預分配空間VS動態分配

不要預分配大數組(如大於64K的元素),其最大的大小,而應該動態分配。在我們這篇文章的效能測試之前,請記住這隻適用部分JavaScript引擎。

空字面量與預分配數組在不同的瀏覽器進行測試

Nitro (Safari)對預分配的數組更有利。而在其他引擎(V8,SpiderMonkey)中,預先分配並不是高效的。

預分配數組測試

// Empty arrayvar arr = [];for (var i = 0; i < 1000000; i++) { arr[i] = i;}// Pre-allocated arrayvar arr = new Array(1000000);for (var i = 0; i < 1000000; i++) { arr[i] = i;}
最佳化你的應用

在Web應用的世界中,速度就是一切。沒有使用者希望用一個要花幾秒鐘計算某列總數或花幾分鐘匯總資訊的表格應用。這是為什麼你要在代碼中壓榨每一點效能的重要原因。

圖片來源: Per Olof Forsberg.

理解和提高應用程式的效能是非常有用的同時,它也是困難的。我們推薦以下的步驟來解決效能的痛點:

  • 測量:在您的應用程式中找到慢的地方(約45%)
  • 理解:找出實際的問題是什麼(約45%)
  • 修複它! (約10%)

下面推薦的一些工具和技術可以協助你。

基準化(BENCHMARKING)

有很多方式來運行JavaScript程式碼片段的基準測試其效能——一般的假設是,基準簡單地比較兩個時間戳記。這中模式被jsPerf團隊指出,並在SunSpider和Kraken的基準套件中使用:

var totalTime, start = new Date, iterations = 1000;while (iterations--) { // Code snippet goes here}// totalTime → the number of milliseconds taken // to execute the code snippet 1000 timestotalTime = new Date - start;

在這裡,要測試的代碼被放置在一個迴圈中,並運行一個設定的次數(例如6次)。在此之後,開始日期減去結束日期,就得出在迴圈中執行操作所花費的時間。

然而,這種基準測試做的事情過於簡單了,特別是如果你想運行在多個瀏覽器和環境的基準。垃圾收集器本身對結果是有一定影響的。即使你使用window.performance這樣的解決方案,也必須考慮到這些缺陷。

不管你是否只運行基準部分的代碼,編寫一個測試套件或編碼基準庫,JavaScript基準其實比你想象的更多。如需更詳細的指南基準,我強烈建議你閱讀由Mathias Bynens和John-David Dalton提供的Javascript基準測試。

分析(PROFILING)

Chrome開發人員工具為JavaScript分析有很好的支援。可以使用此功能檢測哪些函數佔用了大部分時間,這樣你就可以去最佳化它們。這很重要,即使是代碼很小的改變會對整體表現產生重要的影響。

Chrome開發人員工具的分析面板

分析過程開始擷取代碼效能基準,然後以時間軸的形式體現。這將告訴我們代碼需要多長時間運行。“Profiles”選項卡給了我們一個更好的視角來瞭解應用程式中發生了什麼。JavaScript CPU分析檔案展示了多少CPU時間被用於我們的代碼,CSS選取器分析檔案展示了多少時間花費在處理選取器上,堆快照顯示多少記憶體正被用於我們的對象。

利用這些工具,我們可以分離、調整和重新分析來衡量我們的功能或操作效能最佳化是否真的起到了效果。

“Profile”選項卡展示了代碼效能資訊。

一個很好的分析介紹,閱讀Zack Grossbart的 JavaScript Profiling With The Chrome Developer Tools。

提示:在理想情況下,若想確保你的分析並未受到已安裝的應用程式或擴充的任何影響,可以使用--user-data-dir <empty_directory>標誌來啟動Chrome。在大多數情況下,這種方法最佳化測試應該是足夠的,但也需要你更多的時間。這是V8標誌能有所協助的。

避免記憶體流失——3快照技術

在Google內部,Chrome開發人員工具被Gmail等團隊大量使用,用來協助發現和排除記憶體流失。

Chrome開發人員工具中的記憶體統計

記憶體統計出我們團隊所關心的私人記憶體使用量、JavaScript堆的大小、DOM節點數量、儲存清理、事件監聽計數器和垃圾收集器正要回收的東西。推薦閱讀Loreena Lee的“3快照”技術。該技術的要點是,在你的應用程式中記錄一些行為,強制記憶體回收,檢查DOM節點的數量有沒有恢複到預期的基準,然後分析三個堆的快照來確定是否有記憶體流失。

單頁面應用的記憶體管理

單頁面應用程式(例如AngularJS,Backbone,Ember)的記憶體管理是非常重要的,它們幾乎永遠不會重新整理頁面。這意味著記憶體流失可能相當明顯。移動終端上的單頁面應用充滿了陷阱,因為裝置的記憶體有限,並在長期運行Email用戶端或社交網路等應用程式。能力愈大責任愈重。

有很多辦法解決這個問題。在Backbone中,確保使用dispose()來處理舊視圖和引用(目前在Backbone(Edge)中可用)。這個函數是最近加上的,移除添加到視圖“event”對象中的處理函數,以及通過傳給view的第三個參數(回調上下文)的model或collection的事件監聽器。dispose()也會被視圖的remove()調用,處理當元素被移除時的主要清理工作。Ember 等其他的庫當檢測到元素被移除時,會清理監聽器以避免記憶體流失。

Derick Bailey的一些明智的建議:

與其瞭解事件與引用是如何工作的,不如遵循的標準規則來管理JavaScript中的記憶體。如果你想載入資料到的一個存滿使用者物件的Backbone集合中,你要清空這個集合使它不再佔用記憶體,那必須這個集合的所有引用以及集合內對象的引用。一旦清楚了所用的引用,資源就會被回收。這就是標準的JavaScript記憶體回收規則。

在文章中,Derick涵蓋了許多使用Backbone.js時的常見記憶體缺陷,以及如何解決這些問題。

Felix Geisendörfer的在Node中調試記憶體流失的教程也值得一讀,尤其是當它形成了更廣泛SPA堆棧的一部分。

減少迴流(REFLOWS)

當瀏覽器重新渲染文檔中的元素時需要 重新計算它們的位置和幾何形狀,我們稱之為迴流。迴流會阻塞使用者在瀏覽器中的操作,因此理解提升迴流時間是非常有協助的。

迴流時間圖表

你應該批量地觸發迴流或重繪,但是要節制地使用這些方法。盡量不處理DOM也很重要。可以使用DocumentFragment,一個輕量級的文檔對象。你可以把它作為一種方法來提取文檔樹的一部分,或建立一個新的文檔“片段”。與其不斷地添加DOM節點,不如使用文檔片段後只執行一次DOM插入操作,以避免過多的迴流。

例如,我們寫一個函數給一個元素添加20個div。如果只是簡單地每次append一個div到元素中,這會觸發20次迴流。

function addDivs(element) { var div; for (var i = 0; i < 20; i ++) { div = document.createElement('div'); div.innerHTML = 'Heya!'; element.appendChild(div); }}

要解決這個問題,可以使用DocumentFragment來代替,我們可以每次添加一個新的div到裡面。完成後將DocumentFragment添加到DOM中只會觸發一次迴流。

function addDivs(element) { var div;  // Creates a new empty DocumentFragment. var fragment = document.createDocumentFragment(); for (var i = 0; i < 20; i ++) { div = document.createElement('a'); div.innerHTML = 'Heya!'; fragment.appendChild(div); } element.appendChild(fragment);}

可以參閱 Make the Web Faster,JavaScript Memory Optimization 和 Finding Memory Leaks。

JS記憶體流失探測器

為了協助發現JavaScript記憶體流失,Google的開發人員((Marja Hölttä和Jochen Eisinger)開發了一種工具,它與Chrome開發人員工具結合使用,檢索堆的快照並檢測出是什麼對象導致了記憶體流失。

一個JavaScript記憶體流失偵查工具

有完整的文章介紹了如何使用這個工具,建議你自己到記憶體流失探測器項目頁面看看。

如果你想知道為什麼這樣的工具還沒整合到我們的開發工具,其原因有二。它最初是在Closure庫中協助我們捕捉一些特定的記憶體情境,它更適合作為一個外部工具。

V8最佳化調試和記憶體回收的標誌位

Chrome支援直接通過傳遞一些標誌給V8,以獲得更詳細的引擎最佳化輸出結果。例如,這樣可以追蹤V8的最佳化:

"/Applications/Google Chrome/Google Chrome" --js-flags="--trace-opt --trace-deopt"

Windows使用者可以這樣運行 chrome.exe –js-flags=”–trace-opt –trace-deopt”

在開發應用程式時,下面的V8標誌都可以使用。

  • trace-opt —— 記錄最佳化函數的名稱,並顯示跳過的代碼,因為最佳化器不知道如何最佳化。
  • trace-deopt —— 記錄運行時將要“去最佳化”的代碼。
  • trace-gc —— 記錄每次的記憶體回收。

V8的處理指令碼用*(星號)標識最佳化過的函數,用~(波浪號)表示未最佳化的函數。

如果你有興趣瞭解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.