首先,與其他語言不同,JS的效率很大程度是取決於JS engine的效率。除了引擎實現的優劣外,引擎本身也會為一些特殊的程式碼模式採取一些最佳化的策略。例如FF、Opera和Safari的JS引擎,都對字串的拼接運算(+)做了特別優化。顯然,要獲得最大效率,就必須了解引擎的脾氣,並盡量迎合引擎的口味。所以對於不同的引擎,所做的優化極有可能是背道而馳的。
而如果做跨瀏覽器的web編程,則最大的問題在於IE6(JScript 5.6)!因為在不打hotfix的情況下,JScript引擎的垃圾回收的bug,會導致其在真實應用中的performance跟其他瀏覽器根本不在一個數量級上。因此在這種場合做優化,其實就是為JScript做優化!
所以第一原則就是只需要為IE6(未打補丁的JScript 5.6或更早版本)做最佳化!
如果你的程式已經優化到在IE6下可以接受的效能,那麼基本上在其他瀏覽器上效能就完全沒有問題。
因此,注意我下面講的許多問題在其他引擎上可能完全不同,例如在循環中進行字串拼接,通常認為需要用Array.join的方式,但是由於SpiderMonkey等引擎對字串的「+」運算做了優化,結果使用Array.join的效率反而不如直接用「+」!但是如果考慮IE6,則其他瀏覽器上的這種效率的差異根本不值一提。
JS最佳化與其他語言的最佳化也仍有相同之處。比如說,不要一上來就急吼的做優化,那樣毫無意義。優化的關鍵,仍然是要把精力放在最關鍵的地方,也就是瓶頸上。一般來說,瓶頸總是出現在大規模循環的地方。這倒不是說循環本身有效能問題,而是循環會迅速放大可能存在的效能問題。
所以第二個原則就是以大規模循環體為最主要最佳化物件。
以下的最佳化原則,只在大規模循環中才有意義,在循環體之外做這類最佳化基本上是沒有意義的。
目前絕大多數JS引擎都是解釋執行的,而解釋執行的情況下,在所有操作中,函數呼叫的效率是較低的。此外,過深的prototype繼承鏈或多層次引用也會降低效率。在JScript中,10級引用的開銷大致是一次空函數呼叫開銷的1/2。這兩者的開銷都遠大於簡單操作(如四則運算)。
所以第三個原則就是盡量避免過多的引用層級和不必要的多次方法呼叫。
特別要注意的是,有些情況下看似是屬性訪問,實際上是方法調用。例如所有DOM的屬性,其實都是方法。在遍歷一個NodeList的時候,循環條件對於nodes.length的訪問,看似屬性讀取,實際上是等價於函數調用的。而且IE DOM的實作上,childNodes.length每次都是要透過內部遍歷重新計數的。 (My god,但這是真的!因為我測過,childNodes.length的存取時間與childNodes.length的值成正比!)這非常耗費。所以預先把nodes.length存到js變量,當然可以提高遍歷的效能。
同樣是函數調用,用戶自訂函數的效率又遠低於語言內建函數,因為後者是對引擎本地方法的包裝,而引擎通常是c, c++,java寫的。進一步,同樣的功能,語言內建構造的開銷通常又比內建函數呼叫要效率高,因為前者在JS程式碼的parse階段就可以確定和最佳化。
所以第四原則就是盡量使用語言本身的建構與內建函數。
這裡有一個例子是高效能的String.format方法。 String.format傳統的實作方式是用String.replace(regex, func),在pattern包含n個佔位符(包括重複的)時,自訂函數func就被呼叫n次。而這個高效能實作中,每次format呼叫所做的只是一次Array.join然後一次String.replace(regex, string)的操作,兩者都是引擎內建方法,而不會有任何自訂函數呼叫。兩次內建方法調用和n次的自訂方法調用,這就是效能上的差異。
同樣是內建特性,性能上還是有差異的。例如在JScript中對於arguments的存取效能就很差,幾乎趕上一次函數呼叫了。因此如果一個可變參數的簡單函數成為效能瓶頸的時候,可以將其內部做一些改變,不要存取arguments,而是透過對參數的明確判斷來處理。
例如:
Java程式碼
function sum() { var r = 0; for (var i = 0; i < arguments.length; i++) { r += arguments[i]; } return r; }
這個sum通常呼叫的時候個數是較少的,我們希望改進它在參數較少時的效能。若改成:
Java程式碼
function sum() { switch (arguments.length) { case 1: return arguments[0]; case 2: return arguments[0] + arguments[1]; case 3: return arguments[0] + arguments[1] + arguments[2]; case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3]; default: var r = 0; for (var i = 0; i < arguments.length; i++) { r += arguments[i]; } return r; } }
#其實不會有多少提高,但如果改成:
Java代码
function sum(a, b, c, d, e, f, g) { var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0; if (g === undefined) return r; for (var i = 6; i < arguments.length; i++) { r += arguments[i]; } return r; }
就会提高很多(至少快1倍)。
最后是第五原则,也往往是真实应用中最重要的性能障碍,那就是尽量减少不必要的对象创建。
本身创建对象是有一定的代价的,但是这个代价其实并不大。最根本的问题是由于JScript愚蠢之极的垃圾回收调度算法,导致随着对象个数的增加,性能严重下降(据微软的人自己说复杂度是O(n^2))。
比如我们常见的字符串拼接问题,经过我的测试验证,单纯的多次创建字符串对象其实根本不是性能差的原因。要命的是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了那该死的垃圾回收的开销。
因此,如果我们能把大规模对象创建转化为单一语句,则其性能会得到极大的提高!例如通过构造代码然后eval——实际上PIES项目中正在根据这个想法来做一个专门的大规模对象产生器……
好了上面就是总结的JS优化五大原则。
以上是總結五大JavaScript優化原則的詳細內容。更多資訊請關注PHP中文網其他相關文章!