【譯】編寫高性能JavaScript英文鏈接:Writing Fast, Memory-Efficient JavaScript 很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是專門為需要快速執(zhí)行的大型JavaScript應(yīng)用所設(shè)計的。如果你是一個開發(fā)者,并且關(guān)心內(nèi)存使用情況與頁面性能,你應(yīng)該了解用戶瀏覽器中的JavaScript引擎是如何運作的。無論是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,這樣做可以幫助你更好地優(yōu)化你的應(yīng)用程序。這并不是說應(yīng)該專門為某一瀏覽器或引擎做優(yōu)化,千萬別這么做。 但是,你應(yīng)該問自己幾個問題:
加載快速的網(wǎng)站就像是一輛快速的跑車,需要用到特別定制的零件. 圖片來源: dHybridcars. 編寫高性能代碼時有一些常見的陷阱,在這篇文章中,我們將展示一些經(jīng)過驗證的、更好的編寫代碼方式。 那么,JavaScript在V8里是如何工作的? 如果你對JS引擎沒有較深的了解,開發(fā)一個大型Web應(yīng)用也沒啥問題,就好比會開車的人也只是看過引擎蓋而沒有看過車蓋內(nèi)的引擎一樣。鑒于Chrome是我的瀏覽器首選,所以談一下它的JavaScript引擎。V8是由以下幾個核心部分組成:
垃圾回收 垃圾回收是內(nèi)存管理的一種形式,其實就是一個收集器的概念,嘗試回收不再被使用的對象所占用的內(nèi)存。在JavaScript這種垃圾回收語言中,應(yīng)用程序中仍在被引用的對象不會被清除。 手動消除對象引用在大多數(shù)情況下是沒有必要的。通過簡單地把變量放在需要它們的地方(理想情況下,盡可能是局部作用域,即它們被使用的函數(shù)里而不是函數(shù)外層),一切將運作地很好。 垃圾回收器嘗試回收內(nèi)存. 圖片來源: Valtteri M?ki. 在JavaScript中,是不可能強(qiáng)制進(jìn)行垃圾回收的。你不應(yīng)該這么做,因為垃圾收集過程是由運行時控制的,它知道什么是最好的清理時機(jī)。 “消除引用”的誤解 網(wǎng)上有許多關(guān)于JavaScript內(nèi)存回收的討論都談到delete這個關(guān)鍵字,雖然它可以被用來刪除對象(map)中的屬性(key),但有部分開發(fā)者認(rèn)為它可以用來強(qiáng)制“消除引用”。建議盡可能避免使用delete,在下面的例子中 var o = { x: 1 }; delete o.x; // true o.x; // undefined 你會很容易地在流行的JS庫中找到引用刪除——這是具有語言目的性的。這里需要注意的是避免在運行時修改”hot”對象的結(jié)構(gòu)。JavaScript引擎可以檢測出這種“hot”的對象,并嘗試對其進(jìn)行優(yōu)化。如果對象在生命周期中其結(jié)構(gòu)沒有較大的改變,引擎將會更容易優(yōu)化對象,而delete操作實際上會觸發(fā)這種較大的結(jié)構(gòu)改變,因此不利于引擎的優(yōu)化。 對于null是如何工作也是有誤解的。將一個對象引用設(shè)置為null,并沒有使對象變“空”,只是將它的引用設(shè)置為空而已。使用o.x= null比使用delete會更好些,但可能也不是很必要。 var o = { x: 1 }; o = null; o; // null o.x // TypeError 如果此引用是當(dāng)前對象的最后引用,那么該對象將被作為垃圾回收。如果此引用不是當(dāng)前對象的最后引用,則該對象是可訪問的且不會被垃圾回收。 另外需要注意的是,全局變量在頁面的生命周期里是不被垃圾回收器清理的。無論頁面打開多久,JavaScript運行時全局對象作用域中的變量會一直存在。 var myGlobalNamespace = {};
全局對象只會在刷新頁面、導(dǎo)航到其他頁面、關(guān)閉標(biāo)簽頁或退出瀏覽器時才會被清理。函數(shù)作用域的變量將在超出作用域時被清理,即退出函數(shù)時,已經(jīng)沒有任何引用,這樣的變量就被清理了。 經(jīng)驗法則 為了使垃圾回收器盡早收集盡可能多的對象,不要hold著不再使用的對象。這里有幾件事需要記?。?/p>
函數(shù) 接下來,我們談?wù)労瘮?shù)。正如我們已經(jīng)說過,垃圾收集的工作原理,是通過回收不再是訪問的內(nèi)存塊(對象)。為了更好地說明這一點,這里有一些例子。 function foo() { var bar = new LargeObject(); bar.someCall(); } 當(dāng)foo返回時,bar指向的對象將會被垃圾收集器自動回收,因為它已沒有任何存在的引用了。 對比一下: function foo() { var bar = new LargeObject(); bar.someCall(); return bar; } // somewhere else var b = foo(); 現(xiàn)在我們有一個引用指向bar對象,這樣bar對象的生存周期就從foo的調(diào)用一直持續(xù)到調(diào)用者指定別的變量b(或b超出范圍)。 閉包(CLOSURES) 當(dāng)你看到一個函數(shù),返回一個內(nèi)部函數(shù),該內(nèi)部函數(shù)將獲得范圍外的訪問權(quán),即使在外部函數(shù)執(zhí)行之后。這是一個基本的閉包 —— 可以在特定的上下文中設(shè)置的變量的表達(dá)式。例如: function sum (x) { function sumIt(y) { return x + y; }; return sumIt; } // Usage var sumA = sum(4); var sumB = sumA(3); console.log(sumB); // Returns 7 在sum調(diào)用上下文中生成的函數(shù)對象(sumIt)是無法被回收的,它被全局變量(sumA)所引用,并且可以通過sumA(n)調(diào)用。 讓我們來看看另外一個例子,這里我們可以訪問變量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了,它已經(jīng)是垃圾回收候選人了?!咀g者注:因為largeStr已不存在外部引用了】 定時器 最糟的內(nèi)存泄漏地方之一是在循環(huán)中,或者在setTimeout()/ setInterval()中,但這是相當(dāng)常見的。思考下面的例子: 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!”。如果接著運行 同樣值得牢記的是,setTimeout/setInterval調(diào)用(如函數(shù))中的引用,將需要執(zhí)行和完成,才可以被垃圾收集。 當(dāng)心性能陷阱 永遠(yuǎn)不要優(yōu)化代碼,直到你真正需要?,F(xiàn)在經(jīng)常可以看到一些基準(zhǔn)測試,顯示N比M在V8中更為優(yōu)化,但是在模塊代碼或應(yīng)用中測試一下會發(fā)現(xiàn),這些優(yōu)化真正的效果比你期望的要小的多。 做的過多還不如什么都不做. 圖片來源: Tim Sheerman-Chase. 比如我們想要創(chuàng)建這樣一個模塊:
這個問題有幾個不同的因素,雖然也很容易解決。我們?nèi)绾未鎯?shù)據(jù),如何高效地繪制表格并且append到DOM中,如何更優(yōu)地處理表格事件? 面對這些問題最開始(天真)的做法是使用對象存儲數(shù)據(jù)并放入數(shù)組中,使用jQuery遍歷數(shù)據(jù)繪制表格并append到DOM中,最后使用事件綁定我們期望地點擊行為。 注意:這不是你應(yīng)該做的 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'); }); } }; }(); 這段代碼簡單有效地完成了任務(wù)。 但在這種情況下,我們遍歷的數(shù)據(jù)只是本應(yīng)該簡單地存放在數(shù)組中的數(shù)字型屬性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以這種方式)來生成表格是更優(yōu)的選擇,當(dāng)然,事件代理比單獨綁定每個td具有更高的性能。 要注意雖然jQuery在內(nèi)部使用DocumentFragment,但是在我們的例子中,代碼在循環(huán)內(nèi)調(diào)用append并且這些調(diào)用涉及到一些其他的小知識,因此在這里起到的優(yōu)化作用不大。希望這不會是一個痛點,但請務(wù)必進(jìn)行基準(zhǔn)測試,以確保自己代碼ok。 對于我們的例子,上述的做法帶來了(期望的)性能提升。事件代理對簡單的綁定是一種改進(jìn),可選的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'); }); } }; }(); 接下來看看其他提升性能的方式。你也許曾經(jīng)在哪讀到過使用原型模式比模塊模式更優(yōu),或聽說過使用JS模版框架性能更好。有時的確如此,不過使用它們其實是為了代碼更具可讀性。對了,還有預(yù)編譯!讓我們看看在實踐中表現(xiàn)的如何? 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(); 事實證明,在這種情況下的帶來的性能提升可以忽略不計。模板和原型的選擇并沒有真正提供更多的東西。也就是說,性能并不是開發(fā)者使用它們的原因,給代碼帶來的可讀性、繼承模型和可維護(hù)性才是真正的原因。 更復(fù)雜的問題包括高效地在canvas上繪制圖片和操作帶或不帶類型數(shù)組的像素數(shù)據(jù)。 在將一些方法用在你自己的應(yīng)用之前,一定要多了解這些方案的基準(zhǔn)測試。也許有人還記得JS模版的shoot-off和隨后的擴(kuò)展版。你要搞清楚基準(zhǔn)測試不是存在于你看不到的那些虛擬應(yīng)用,而是應(yīng)該在你的實際代碼中去測試帶來的優(yōu)化。 V8優(yōu)化技巧 詳細(xì)介紹了每個V8引擎的優(yōu)化點在本文討論范圍之外,當(dāng)然這里也有許多值得一提的技巧。記住這些技巧你就能減少那些性能低下的代碼了。
function add(x, y) { return x+y; } add(1, 2); add('a','b'); add(my_custom_object, undefined);
更多內(nèi)容可以去看Daniel Clifford在Google I/O的分享 Breaking the JavaScript Speed Limit with V8。 Optimizing For V8 — A Series也非常值得一讀。 對象VS數(shù)組:我應(yīng)該用哪個?
JavaScript中對象和數(shù)組之間只有一個的主要區(qū)別,那就是數(shù)組神奇的length屬性。如果你自己來維護(hù)這個屬性,那么V8中對象和數(shù)組的速度是一樣快的。 使用對象時的技巧
對象克隆 對于應(yīng)用程序開發(fā)人員,對象克隆是一個常見的問題。雖然各種基準(zhǔn)測試可以證明V8對這個問題處理得很好,但仍要小心。復(fù)制大的東西通常是較慢的——不要這么做。JS中的for..in循環(huán)尤其糟糕,因為它有著惡魔般的規(guī)范,并且無論是在哪個引擎中,都可能永遠(yuǎn)不會比任何對象快。 當(dāng)你一定要在關(guān)鍵性能代碼路徑上復(fù)制對象時,使用數(shù)組或一個自定義的“拷貝構(gòu)造函數(shù)”功能明確地復(fù)制每個屬性。這可能是最快的方式: function clone(original) { this.foo = original.foo; this.bar = original.bar; } var copy = new clone(original); 模塊模式中緩存函數(shù) 使用模塊模式時緩存函數(shù),可能會導(dǎo)致性能方面的提升。參閱下面的例子,因為它總是創(chuàng)建成員函數(shù)的新副本,你看到的變化可能會比較慢。 另外請注意,使用這種方法明顯更優(yōu),不僅僅是依靠原型模式(經(jīng)過jsPerf測試確認(rèn))。 使用模塊模式或原型模式時的性能提升 這是一個原型模式與模塊模式的性能對比測試: // 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 使用數(shù)組時的技巧 接下來說說數(shù)組相關(guān)的技巧。在一般情況下,不要刪除數(shù)組元素,這樣將使數(shù)組過渡到較慢的內(nèi)部表示。當(dāng)索引變得稀疏,V8將會使元素轉(zhuǎn)為更慢的字典模式。 數(shù)組字面量 數(shù)組字面量非常有用,它可以暗示VM數(shù)組的大小和類型。它通常用在體積不大的數(shù)組中。 // 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 array for(var i = 1; i <= 4; i++) { a.push(i); } 存儲單一類型VS多類型 將混合類型(比如數(shù)字、字符串、undefined、true/false)的數(shù)據(jù)存在數(shù)組中絕不是一個好想法。例如var arr = [1, “1”, undefined, true, “true”] 正如我們所看到的結(jié)果,整數(shù)的數(shù)組是最快的。 稀疏數(shù)組與滿數(shù)組 當(dāng)你使用稀疏數(shù)組時,要注意訪問元素將遠(yuǎn)遠(yuǎn)慢于滿數(shù)組。因為V8不會分配一整塊空間給只用到部分空間的數(shù)組。取而代之的是,它被管理在字典中,既節(jié)約了空間,但花費訪問的時間。 預(yù)分配空間VS動態(tài)分配 不要預(yù)分配大數(shù)組(如大于64K的元素),其最大的大小,而應(yīng)該動態(tài)分配。在我們這篇文章的性能測試之前,請記住這只適用部分JavaScript引擎。 空字面量與預(yù)分配數(shù)組在不同的瀏覽器進(jìn)行測試 Nitro (Safari)對預(yù)分配的數(shù)組更有利。而在其他引擎(V8,SpiderMonkey)中,預(yù)先分配并不是高效的。 // Empty array var arr = []; for (var i = 0; i < 1000000; i++) { arr[i] = i; } // Pre-allocated array var arr = new Array(1000000); for (var i = 0; i < 1000000; i++) { arr[i] = i; } 優(yōu)化你的應(yīng)用 在Web應(yīng)用的世界中,速度就是一切。沒有用戶希望用一個要花幾秒鐘計算某列總數(shù)或花幾分鐘匯總信息的表格應(yīng)用。這是為什么你要在代碼中壓榨每一點性能的重要原因。 圖片來源: Per Olof Forsberg. 理解和提高應(yīng)用程序的性能是非常有用的同時,它也是困難的。我們推薦以下的步驟來解決性能的痛點:
下面推薦的一些工具和技術(shù)可以協(xié)助你。 基準(zhǔn)化(BENCHMARKING) 有很多方式來運行JavaScript代碼片段的基準(zhǔn)測試其性能——一般的假設(shè)是,基準(zhǔn)簡單地比較兩個時間戳。這中模式被jsPerf團(tuán)隊指出,并在SunSpider和Kraken的基準(zhǔn)套件中使用: 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 times totalTime = new Date - start; 在這里,要測試的代碼被放置在一個循環(huán)中,并運行一個設(shè)定的次數(shù)(例如6次)。在此之后,開始日期減去結(jié)束日期,就得出在循環(huán)中執(zhí)行操作所花費的時間。 然而,這種基準(zhǔn)測試做的事情過于簡單了,特別是如果你想運行在多個瀏覽器和環(huán)境的基準(zhǔn)。垃圾收集器本身對結(jié)果是有一定影響的。即使你使用window.performance這樣的解決方案,也必須考慮到這些缺陷。 不管你是否只運行基準(zhǔn)部分的代碼,編寫一個測試套件或編碼基準(zhǔn)庫,JavaScript基準(zhǔn)其實比你想象的更多。如需更詳細(xì)的指南基準(zhǔn),我強(qiáng)烈建議你閱讀由Mathias Bynens和John-David Dalton提供的Javascript基準(zhǔn)測試。 分析(PROFILING) Chrome開發(fā)者工具為JavaScript分析有很好的支持??梢允褂么斯δ軝z測哪些函數(shù)占用了大部分時間,這樣你就可以去優(yōu)化它們。這很重要,即使是代碼很小的改變會對整體表現(xiàn)產(chǎn)生重要的影響。 Chrome開發(fā)者工具的分析面板 分析過程開始獲取代碼性能基線,然后以時間線的形式體現(xiàn)。這將告訴我們代碼需要多長時間運行?!癙rofiles”選項卡給了我們一個更好的視角來了解應(yīng)用程序中發(fā)生了什么。JavaScript CPU分析文件展示了多少CPU時間被用于我們的代碼,CSS選擇器分析文件展示了多少時間花費在處理選擇器上,堆快照顯示多少內(nèi)存正被用于我們的對象。 利用這些工具,我們可以分離、調(diào)整和重新分析來衡量我們的功能或操作性能優(yōu)化是否真的起到了效果。 “Profile”選項卡展示了代碼性能信息。 一個很好的分析介紹,閱讀Zack Grossbart的 JavaScript Profiling With The Chrome Developer Tools。 提示:在理想情況下,若想確保你的分析并未受到已安裝的應(yīng)用程序或擴(kuò)展的任何影響,可以使用 避免內(nèi)存泄漏——3快照技術(shù) 在谷歌內(nèi)部,Chrome開發(fā)者工具被Gmail等團(tuán)隊大量使用,用來幫助發(fā)現(xiàn)和排除內(nèi)存泄漏。 Chrome開發(fā)者工具中的內(nèi)存統(tǒng)計 內(nèi)存統(tǒng)計出我們團(tuán)隊所關(guān)心的私有內(nèi)存使用、JavaScript堆的大小、DOM節(jié)點數(shù)量、存儲清理、事件監(jiān)聽計數(shù)器和垃圾收集器正要回收的東西。推薦閱讀Loreena Lee的“3快照”技術(shù)。該技術(shù)的要點是,在你的應(yīng)用程序中記錄一些行為,強(qiáng)制垃圾回收,檢查DOM節(jié)點的數(shù)量有沒有恢復(fù)到預(yù)期的基線,然后分析三個堆的快照來確定是否有內(nèi)存泄漏。 單頁面應(yīng)用的內(nèi)存管理 單頁面應(yīng)用程序(例如AngularJS,Backbone,Ember)的內(nèi)存管理是非常重要的,它們幾乎永遠(yuǎn)不會刷新頁面。這意味著內(nèi)存泄漏可能相當(dāng)明顯。移動終端上的單頁面應(yīng)用充滿了陷阱,因為設(shè)備的內(nèi)存有限,并在長期運行Email客戶端或社交網(wǎng)絡(luò)等應(yīng)用程序。能力愈大責(zé)任愈重。 有很多辦法解決這個問題。在Backbone中,確保使用dispose()來處理舊視圖和引用(目前在Backbone(Edge)中可用)。這個函數(shù)是最近加上的,移除添加到視圖“event”對象中的處理函數(shù),以及通過傳給view的第三個參數(shù)(回調(diào)上下文)的model或collection的事件監(jiān)聽器。dispose()也會被視圖的remove()調(diào)用,處理當(dāng)元素被移除時的主要清理工作。Ember 等其他的庫當(dāng)檢測到元素被移除時,會清理監(jiān)聽器以避免內(nèi)存泄漏。 Derick Bailey的一些明智的建議:
在文章中,Derick涵蓋了許多使用Backbone.js時的常見內(nèi)存缺陷,以及如何解決這些問題。 Felix Geisend?rfer的在Node中調(diào)試內(nèi)存泄漏的教程也值得一讀,尤其是當(dāng)它形成了更廣泛SPA堆棧的一部分。 減少回流(REFLOWS) 當(dāng)瀏覽器重新渲染文檔中的元素時需要 重新計算它們的位置和幾何形狀,我們稱之為回流?;亓鲿枞脩粼跒g覽器中的操作,因此理解提升回流時間是非常有幫助的。 回流時間圖表 你應(yīng)該批量地觸發(fā)回流或重繪,但是要節(jié)制地使用這些方法。盡量不處理DOM也很重要??梢允褂?a href="http://www./TR/DOM-Level-2-Core/core.html#ID-B63ED1A3" target="_blank">DocumentFragment,一個輕量級的文檔對象。你可以把它作為一種方法來提取文檔樹的一部分,或創(chuàng)建一個新的文檔“片段”。與其不斷地添加DOM節(jié)點,不如使用文檔片段后只執(zhí)行一次DOM插入操作,以避免過多的回流。 例如,我們寫一個函數(shù)給一個元素添加20個div。如果只是簡單地每次append一個div到元素中,這會觸發(fā)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中只會觸發(fā)一次回流。 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內(nèi)存泄漏探測器 為了幫助發(fā)現(xiàn)JavaScript內(nèi)存泄漏,谷歌的開發(fā)人員((Marja H?ltt?和Jochen Eisinger)開發(fā)了一種工具,它與Chrome開發(fā)人員工具結(jié)合使用,檢索堆的快照并檢測出是什么對象導(dǎo)致了內(nèi)存泄漏。 一個JavaScript內(nèi)存泄漏檢測工具 有完整的文章介紹了如何使用這個工具,建議你自己到內(nèi)存泄漏探測器項目頁面看看。 如果你想知道為什么這樣的工具還沒集成到我們的開發(fā)工具,其原因有二。它最初是在Closure庫中幫助我們捕捉一些特定的內(nèi)存場景,它更適合作為一個外部工具。 V8優(yōu)化調(diào)試和垃圾回收的標(biāo)志位 Chrome支持直接通過傳遞一些標(biāo)志給V8,以獲得更詳細(xì)的引擎優(yōu)化輸出結(jié)果。例如,這樣可以追蹤V8的優(yōu)化: "/Applications/Google Chrome/Google Chrome" --js-flags="--trace-opt --trace-deopt" Windows用戶可以這樣運行 chrome.exe –js-flags=”–trace-opt –trace-deopt” 在開發(fā)應(yīng)用程序時,下面的V8標(biāo)志都可以使用。
V8的處理腳本用*(星號)標(biāo)識優(yōu)化過的函數(shù),用~(波浪號)表示未優(yōu)化的函數(shù)。 如果你有興趣了解更多關(guān)于V8的標(biāo)志和V8的內(nèi)部是如何工作的,強(qiáng)烈建議 閱讀Vyacheslav Egorov的excellent post on V8 internals。 HIGH-RESOLUTION TIME 和 NAVIGATION TIMING API 高精度時間(HRT)是一個提供不受系統(tǒng)時間和用戶調(diào)整影響的亞毫秒級高精度時間接口,可以把它當(dāng)做是比 new Date 和 Date.now()更精準(zhǔn)的度量方法。這對我們編寫基準(zhǔn)測試幫助很大。 高精度時間(HRT)提供了當(dāng)前亞毫秒級的時間精度 目前HRT在Chrome(穩(wěn)定版)中是以window.performance.webkitNow()方式使用,但在Chrome Canary中前綴被丟棄了,這使得它可以通過window.performance.now()方式調(diào)用。Paul Irish在HTML5Rocks上了關(guān)于HRT更多內(nèi)容的文章。 現(xiàn)在我們知道當(dāng)前的精準(zhǔn)時間,那有可以準(zhǔn)確測量頁面性能的API嗎?好吧,現(xiàn)在有個Navigation Timing API可以使用,這個API提供了一種簡單的方式,來獲取網(wǎng)頁在加載呈現(xiàn)給用戶時,精確和詳細(xì)的時間測量記錄??梢栽赾onsole中使用window.performance.timing來獲取時間信息: 顯示在控制臺中的時間信息 我們可以從上面的數(shù)據(jù)獲取很多有用的信息,例如網(wǎng)絡(luò)延時為responseEnd – fetchStart,頁面加載時間為loadEventEnd – responseEnd,處理導(dǎo)航和頁面加載的時間為loadEventEnd – navigationStart。 正如你所看到的,perfomance.memory的屬性也能顯示JavaScript的內(nèi)存數(shù)據(jù)使用情況,如總的堆大小。 更多Navigation Timing API的細(xì)節(jié),閱讀 Sam Dutton的 Measuring Page Load Speed With Navigation Timing。 ABOUT:MEMORY 和 ABOUT:TRACING Chrome中的about:tracing提供了瀏覽器的性能視圖,記錄了Chrome的所有線程、tab頁和進(jìn)程。
這個工具的真正用處是允許你捕獲Chrome的運行數(shù)據(jù),這樣你就可以適當(dāng)?shù)卣{(diào)整JavaScript執(zhí)行,或優(yōu)化資源加載。 Lilli Thompson有一篇寫給游戲開發(fā)者的使用about:tracing分析WebGL游戲的文章,同時也適合JavaScript的開發(fā)者。 在Chrome的導(dǎo)航欄里可以輸入about:memory,同樣十分實用,可以獲得每個tab頁的內(nèi)存使用情況,對定位內(nèi)存泄漏很有幫助。 總結(jié) 我們看到,JavaScript的世界中有很多隱藏的陷阱,且并沒有提升性能的銀彈。只有把一些優(yōu)化方案綜合使用到(現(xiàn)實世界)測試環(huán)境,才能獲得最大的性能收益。即便如此,了解引擎是如何解釋和優(yōu)化代碼,可以幫助你調(diào)整應(yīng)用程序。 測量,理解,修復(fù)。不斷重復(fù)這個過程。 圖片來源: Sally Hunter 謹(jǐn)記關(guān)注優(yōu)化,但為了便利可以舍棄一些很小的優(yōu)化。例如,有些開發(fā)者選擇.forEach和Object.keys代替for和for..in循環(huán),盡管這會更慢但使用更方便。要保證清醒的頭腦,知道什么優(yōu)化是需要的,什么優(yōu)化是不需要的。 同時注意,雖然JavaScript引擎越來越快,但下一個真正的瓶頸是DOM?;亓骱椭乩L的減少也是重要的,所以必要時再去動DOM。還有就是要關(guān)注網(wǎng)絡(luò),HTTP請求是珍貴的,特別是移動終端上,因此要使用HTTP的緩存去減少資源的加載。 記住這幾點可以保證你獲取了本文的大部分信息,希望對你有所幫助! |
|