原文連接:https://dev.opera.com/articles/efficient-javascript/?page=2#primitiveo...javascript
曾經,一個Web頁面不會包含太多的腳本,或者至少來講,它們不會影響頁面的性能。然而,如今的Web頁面愈來愈像本地運用了,腳本的性能成了一個很大的影響隨着愈來愈多的運用轉向使用Web技術時,提升頁面的性能成爲了愈來愈重要的問題。java
eval
和Function構造函數
每次當eval
和Function constructor
經過字符串源碼形式調用時,腳本引擎必須開啓轉換機制將字符串源碼轉換成可執行的代碼。一般來講這是比較耗性能的。瀏覽器
eval
調用特別的很差,當執行的內容字符串傳遞給eval
時不能被提早執行,因爲代碼執行會被eval
中執行的內容影響,那就意味着編譯器不能更好的優化執行上下文,而且瀏覽器在運行時時放棄執行下面的上下文。這樣就增長了額外的性能影響。函數
對於Function constructor
來講,它的名聲和eval
同樣也不太好,雖然使用它並不會影響上下文的執行,可是它執行的效率卻很低下。示例代碼以下:性能
錯誤的使用eval
:優化
function getProperty(oString) { var oReference; eval('oReference = test.prop.' + oString); return oReference; }
正確的姿式:this
function getProperty(oString) { return test.prop[oString]; }
錯誤的使用Function constructor
:code
function addMethod(oObject, oProperty, oFunctionCode) { oObject[oProperty] = new Function(oFunctionCode); } addMethod( myObject, 'rotateBy90', 'this.angle = (this.angle + 90) % 360' ); addMethod( myObject, 'rotateBy60', 'this.angle = (this.angle + 60) % 360' );
正確的姿式:orm
function addMethod(oObject, oProperty, oFunction) { oObject[oProperty] = oFunction; } addMethod( myObject, 'rotateBy90', function() { this.angle = (this.angle + 90) % 360; } ); addMethod( myObject, 'rotateBy60', function() { this.angle = (this.angle + 60) % 360; } );
with
儘管對於開發人員來講,使用with
比較方便,可是對性能來講,倒是很是消耗的。緣由是對腳本引擎來講,它會拓展做用域鏈,而查找變量的時候不會判斷是否被當前引用。儘管這種狀況帶來性能的開銷比較少,可是每次編譯的時候咱們都不知道內容的做用域,那就意味着編譯器不能對其進行優化,因此它就和普通的做用域同樣。對象
一種更有效的方法代替方法是使用一個對象變量來代替with
的使用。屬性的訪問能夠經過對象的引用來實現。這樣工做起來很是有效,若是屬性不是基本類型外,好比字符串和布爾值。
考慮下面的代碼:
with(test.information.settings.files) { primary = 'names'; secondary = 'roles'; tertiary = 'references'; }
使用下面的方式效率更高:
var testObject = test.information.settings.files; testObject.primary = 'names'; testObject.secondary = 'roles'; testObject.tertiary = 'references';
try-catch-finally
try-catch-finally
語句相對於其餘的語句來講它的結構很是惟一的,當腳本運行的時候它會在當前的做用域總建立一個變量,這發生在catch
語句調用的時候.捕獲的異常對象會關聯這個變量,這個變量不會存在其餘的腳本里,即便是相同的做用域。它在catch
語句開始的時候建立,在執行結束的時候銷燬它。
由於這個變量會在運行的時候建立和銷燬,因此會產生一種特殊的狀況,一些瀏覽器不能及時的在性能比較耗的循環中及時捕獲該句柄。因此會致使一些性能上的問題當異常被捕獲的時候。
若是可能的話,異常的執行應該在更高的級別執行,這樣它就不會頻繁的出現,或者經過檢查指望的動做最先被容許的話來避免,下面經過示例來講明:
錯誤的使用方式:
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; for(var i = 0; i < oProperties.length; i++) { try { test[oProperties[i]].someproperty = somevalue; } catch(e) { … } }
在許多狀況下,try-catch-finally
結構能夠移動到循環的外圍, 這樣看起來彷佛語意上有點改變。因爲異常拋出時,循環會被中斷,可是下面的代碼會依然執行。
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; try { for(var i = 0; i < oProperties.length; i++) { test[oProperties[i]].someproperty = somevalue; } } catch(e) { … }
在某些狀況下,try-catch-finally
結構能夠避免使用。好比:
var oProperties = [ 'first', 'second', 'third', … 'nth' ]; for(var i = 0; i < oProperties.length; i++) { if(test[oProperties[i]]) { test[oProperties[i]].someproperty = somevalue; } }
eval
和with
的使用因爲這些結構影響性能如此的深,因此它們使用的越少越好。可是有時候你可能須要它們,若是一個函數被調用或者一個循環重複的被計算,最好的方式仍是避免使用這些結構,他們最好的解決方案就是被執行一次,或者極少數,以致於基本上對性能沒什麼影響。
在全局範圍內建立一個變量是很誘惑的,只因它建立的方式很簡單,可是有一下幾個緣由會致使腳本運行變慢。
首先,全局變量須要腳本引擎查找到最外的做用域,查找速度比較慢,第二,全局變量經過window對象被分享,意味着本質上它有兩層做用域(??)。
字面量,好比字符串,數字或者布爾值,在ECMAScript
中有兩種表現,它門可能被看成單純的值或者一個對象。
任何屬性或者方法被調用的時候針對的是這個對象,不是這個值,當你引用一個屬性或者方法的時候,ECMAScript
引擎會暗中的建立一個你值對應的字符串對象。在方法調用以前。這個對象只會被請求一次,當你嘗試下一次調用該值的某個方法時它又會被建立一次。來看看下面的例子:
var s = '0123456789'; for(var i = 0; i < s.length; i++) { s.charAt(i); }
上面的例子須要腳本引擎建立21次字符串對象,一次length
屬性的訪問,一次charAt
方法的調用。
優化的方案以下所示:
var s = new String('0123456789'); for(var i = 0; i < s.length; i++) { s.charAt(i); }
和上面等效,可是僅僅手動建立了一個對象,性能上要比上面的好不少。
注意:不一樣的瀏覽器,對於裝箱和拆箱的優化不同。
for-in
迭代for-in
迭代有它本身的特色,可是它常常被濫用,這種迭代須要腳本引擎建立一個全部可枚舉屬性的清單,並檢出爲看成副本,在開始枚舉的時候。
字符串的拼接是個昂貴的過程,當使用"+
"運算符時,它不會把結果當即添加到變量中,反而它會建立一個新的字符串對象在內存中,並把獲得的結果賦值個這個字符串。而後這個新的字符串對象在賦值給變量。可是使用"+=
"能夠避免這樣的過程。
示例:
var min = Math.min(a,b); A.push(v);
下面的方式和上面的等效,可是效率更高:
var min = a < b ? a : b; A[A.length] = v;
setTimeout()
和setInterval()
當setTimeout()
和setInterval()
方法傳遞的是個字符串時,它內部會調用eval
,因此會致使性能上的問題。