天天看點

《高性能javascript》學習筆記四

四、算法和流程控制

1、循環的分析

循環的類型,可以參考我之前的文章:《數組周遊的幾種方法及用法》,除此之外,還有最基礎的for循環,while循環,do while循環,以及周遊對象的for in 循環(for in循環周遊的順序是按照鍵值對加入對象的時間從先到後周遊的,并且會周遊繼承自原型的屬性。建議用for of 周遊Object.keys()或者Objec)。

在這些循環/周遊的方法中,for循環、while循環、do while循環的方式,單次循環所需的時間最少,而for in周遊的方式是最慢的,因為for in循環的每次疊代都會從原型鍊上尋找。

雖然for循環、while循環、do while循環的性能消耗最少,但是,對于數組或者對象的周遊,通常情況下還是推薦forEach等數組/對象自帶的周遊方式,因為這樣代碼看起來簡潔很多。

在循環的疊代過程中,優化性能的第一步是減少對象成員或者數組項成員的查找次數。這可以通過之前介紹的使用局部變量緩存資料來達到。

值得一提的是,通常情況下,倒序循環會略微提升性能,是以,如果循環的順序沒有影響,可以考慮使用倒序。以這份代碼為例:

《高性能javascript》學習筆記四

下面的循環方式是更節約性能的。

每次循環時,上方的for循環會比較兩次:與數組長度比較,然後判定結果是否為true;下方的for循環隻會比較一次:這個值是否為true。

當循環次數複雜度為O(n)時,應注重減少每次疊代的工作量;複雜度大于O(n)時,應注重減少疊代次數。

2、循環的優化

循環體運作時會帶來一次小的性能開銷,是以,減少疊代次數能獲得比較顯著的性能提升。

比較常用的限制循環疊代次數的模式叫做達夫裝置(Duffs Device),

典型實作如下:

《高性能javascript》學習筆記四

基本理念是:每次循環最多調用6次函數,是以整個循環的疊代次數将是總數除以6.這會減少因執行循環體帶來的額外開銷。

3、條件語句:

if else對比switch

當條件數量越大時,建議用switch,因為代碼的可讀性更高。條件較少時用if else,原因也是可讀性更高。當然這兩種方法性能并沒有顯著差别。當條件增加時,switch稍快。

條件語句的優化思路是,近可能的讓正确分支放在前面。是以,如果對可能的結果有所預估,應該讓正确的判斷條件放在前面。這将減少判斷的次數。。

另外一種方法是,對于if else,可以寫成一系列嵌套的if else語句(參考二分法),這也會減少判斷的次數。

當判斷條件是一組較大的離散值時,利用數組或者對象是一個不錯的選擇。這适用于單個鍵和單個值存在映射關系的場景。

4、遞歸

遞歸是常用的一種算法實作方式。例如階乘、斐波那契數列等。

《高性能javascript》學習筆記四

使用遞歸時需要非常小心,因為遞歸非常因為缺少終止條件造成死循環,進而導緻超出調用棧大小限制或者頁面崩潰。

5、疊代(循環)

任何用遞歸實作的算法也可以用疊代(循環)來實作。

例如歸并排序

《高性能javascript》學習筆記四

這個算法會造成merageSort()頻繁的自調用。

下面是用疊代實作該算法:

《高性能javascript》學習筆記四

6、優化疊代的方式

在某些情況下,每一次循環都會進行重複的工作,是以,在适當的時候避免重複工作是提升性能的一個很好的方法。以之前的階乘函數為例。

《高性能javascript》學習筆記四

想象一下,如果要計算6,7,8的階乘,都調用fun函數的話,1-6的階乘其實被重複調用了3次,7的階乘被調用了2次。是以,更好的方法是緩存并重用他們的結果。一下是改良版:

《高性能javascript》學習筆記四

優化的關鍵是為這個方法綁定了一個緩存對象用于儲存階乘的值,以便在後續調用時,在需要的時候可以直接擷取,而非重新計算。

在此基礎上,我們可以封裝一個公共的、用于緩存任意函數計算結果的函數。

《高性能javascript》學習筆記四

cache是初始的緩存對象。這個函數會傳回一個對原有函數的封裝函數,給每一個計算函數聲明一個緩存對象,将計算過的結果儲存在對象裡,以便複用。

注意,這個公告緩存函數的優化效果比上方的階乘優化函數的優化效果更差,因為公告緩存函數隻會緩存特定參數的結果,類似階乘函數,計算8的階乘時不會複用之前的1-7的階乘結果,隻會檢視之前是否計算過8的階乘,然後決定是複用還是計算。是以,遇到較為嚴重的性能問題時,最好針對性的寫一個優化函數。