用戶端指令碼能夠使得應用更加具有吸引力,但是瀏覽器對指令碼代碼的解釋執行可能也會導致效率低下。
這裡我們探討幾個最佳化JavaScript代碼的最佳實務建議。
1、處理字串
字串拼接在IE 6、7下的記憶體回收效能很差。雖然IE 8已經解決了這個問題。如果你的使用者中有相當一部分人使用IE 6、7,那麼你得謹慎構建你的字串了。
比如這個例子:
var veryLongMessage ='This is a long string that due to our strict line length limit of' +maxCharsPerLine +' characters per line must be wrapped. ' +percentWhoDislike +'% of engineers dislike this rule. The line length limit is for ' +' style purposes, but we don't want it to have a performance impact.' +' So the question is how should we do the wrapping?';
不要直接使用字串拼接 + ,更好的做法是用join:
var veryLongMessage =['This is a long string that due to our strict line length limit of',maxCharsPerLine,' characters per line must be wrapped. ',percentWhoDislike,'% of engineers dislike this rule. The line length limit is for ',' style purposes, but we don't want it to have a performance impact.',' So the question is how should we do the wrapping?'].join();
類似的,在條件分支語句或迴圈語句中使用字串拼接也很低效。不好的做法:
var fibonacciStr = 'First 20 Fibonacci Numbers';for (var i = 0; i < 20; i++) {fibonacciStr += i + ' = ' + fibonacci(i) + '';}
正確的做法:
var strBuilder = ['First 20 fibonacci numbers:'];for (var i = 0; i < 20; i++) { strBuilder.push(i, ' = ', fibonacci(i));}var fibonacciStr = strBuilder.join('');
2、使用helper functions構建字串片段
通過將string builder傳入helper function來避免臨時儲存消耗。
例如不該用下面這種:
var strBuilder = [];for (var i = 0, length = menuItems.length; i < length; i++) { strBuilder.push(this.buildMenuItemHtml_(menuItems[i]));}var menuHtml = strBuilder.join();
而應該使用:
var strBuilder = [];for (var i = 0, length = menuItems.length; i < length; i++) { this.buildMenuItem_(menuItems[i], strBuilder);}var menuHtml = strBuilder.join();
3、定義類方法
下面的代碼是低效的,因為每次有一個baz.Bar執行個體被建立,一個新的函數和閉包就為foo建立:
baz.Bar = function() { // constructor body this.foo = function() { // method body };}
更優的做法是:
baz.Bar = function() { // constructor body};baz.Bar.prototype.foo = function() { // method body};
通過這種方法,不管有多少個baz.Bar執行個體被建立,只有一個function被建立,並且也不形成閉包。
4、初始化執行個體變數
將執行個體變數的聲明/初始化放到prototype中,如果執行個體變數帶有一個實值型別的初始值(即number、Boolean、null、undefined或字串類型值)。這避免了每次構造器建立時都運行毫無意義的初始化代碼。
不好的做法:
foo.Bar = function() { this.prop1_ = 4; this.prop2_ = true; this.prop3_ = []; this.prop4_ = 'blah';};
更優的做法:
foo.Bar = function() { this.prop3_ = [];};foo.Bar.prototype.prop1_ = 4;foo.Bar.prototype.prop2_ = true;foo.Bar.prototype.prop4_ = 'blah';
5、避免閉包中的陷阱
閉包是一個強大有用的東西。但是他們有一些缺點,包括:
1、他們是記憶體泄露的最主要元兇2、建立一個閉包比建立一個內嵌函式慢,比重用一個靜態函數更慢。
比如:
function setupAlertTimeout() { var msg = 'Message to alert'; window.setTimeout(function() { alert(msg); }, 100);}
比下面的代碼更慢:
function setupAlertTimeout() { window.setTimeout(function() { var msg = 'Message to alert'; alert(msg); }, 100);}
上面的代碼又比下面的代碼更慢:
function alertMsg() { var msg = 'Message to alert'; alert(msg);}function setupAlertTimeout() { window.setTimeout(alertMsg, 100);}
閉包增加了一層範圍鏈。當瀏覽器解析屬性時,每一層的範圍鏈都必須被檢查。在下面的例子:
var a = 'a';function createFunctionWithClosure() { var b = 'b'; return function () { var c = 'c'; a; b; c; };}var f = createFunctionWithClosure();f();
當f被調用時,引用a比引用b更慢,引用b又比引用c更慢。
查看IE+JScript Performance Recommendations Part 3: JavaScript Code inefficiencies 來擷取更多在IE下正確使用閉包的資訊
6、避免with
避免在代碼中使用with語句。它對效能產生負面影響,因為它改變了範圍鏈,使得在其它範圍中尋找變數的代價更加昂貴。
7、避免瀏覽器記憶體泄露
記憶體泄露是web應用中的常見問題,會導致巨大的效能問題。當瀏覽器消耗的記憶體增加,你的其他Web應用以及使用者系統的其他程式都會變得很慢。最常見的記憶體泄露情境是JavaScript引擎和瀏覽器的DOM對象的循環相依性,比如下面這個例子:
<script language='javascript'> var menu = document.getElementById('myMenu'); AttachEvent(menu); function AttachEvent(element) { element.attachEvent( "onmouseover", mouseHandler); function mouseHandler(){ /* whatever */ } }</script>
又比如下面的代碼:
var myGlobalObject;function SetupLeak(){ //Here a reference created from the JS World //to the DOM world. myGlobalObject=document.getElementById("LeakedDiv"); //Here DOM refers back to JS World; //hence a circular reference. //The memory will leak if not handled properly. document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;}
更多資訊可以參考:http://www.javascriptkit.com/javatutors/closuresleak/index.shtml