JS效能優化38條"軍規",2019年嘔心力作

Soniaxu發表於2019-03-01

前言

先自我介紹下,Sonia,女,資深前端開發一枚,現就職於某魚技術部,負責主站效能優化和重構工作。掐指一算,從事前端開發整整12個年頭了,很多朋友經常問到程式碼優化及效能除錯,這裡給大家總結了一下JS開發中的一些程式碼規範及優化的技巧和經驗,此文談不上精品之作,但絕對是匠心之作,誠心之作,希望給前端開發路上的你一點幫助。

1. 避免全域性查詢

在一個函式中會用到全域性物件儲存為區域性變數來減少全域性查詢,因為訪問區域性變數的速度要比訪問全域性變數的速度更快些。

JS效能優化38條"軍規",2019年嘔心力作

2. 定時器

如果針對的是不斷執行的程式碼,不應該使用setTimeout,而應該是用setInterval,因為setTimeout每一次都會初始化一個定時器,而setInterval只會在開始的時候初始化一個定時器。

JS效能優化38條"軍規",2019年嘔心力作

3. 字串連線

如果要連線多個字串,應該少使用+=,如:

x+=a; x+=b; x+=c; 應該寫成 x+= a + b + c; 而如果是收集字串,比如多次對同一個字串進行+=操作的話,最好使用一個快取,使用JavaScript陣列來收集,最後使用join方法連線起來。

JS效能優化38條"軍規",2019年嘔心力作

4. 避免with語句

和函式類似 ,with語句會建立自己的作用域,因此會增加其中執行的程式碼的作用域鏈的長度,由於額外的作用域鏈的查詢,在with語句中執行的程式碼肯定比外面執行的程式碼要慢,在能不使用with語句的時候儘量不要使用with語句。

JS效能優化38條"軍規",2019年嘔心力作

5. 數字轉換成字串

一般最好用"" + 1來將數字轉換成字串,雖然看起來比較醜一點,但事實上這個效率是最高的,效能上來說:("" +) > String() > .toString() > new String()

6. 浮點數轉換成整型

很多人喜歡使用parseInt(),其實parseInt()是用於將字串轉換成數字,而不是浮點數和整型之間的轉換,我們應該使用Math.floor()或者Math.round()。

7. 各種型別轉換

JS效能優化38條"軍規",2019年嘔心力作

如果定義了toString()方法來進行型別轉換的話,推薦顯式呼叫toString(),因為內部的操作在嘗試所有可能性之後,會嘗試物件的toString()方法嘗試能否轉化為String,所以直接呼叫這個方法效率會更高

8. 多個型別宣告

在JavaScript中所有變數都可以使用單個var語句來宣告,這樣就是組合在一起的語句,以減少整個指令碼的執行時間,就如上面程式碼一樣,上面程式碼格式也挺規範,讓人一看就明瞭。

9. 插入迭代器

如var a=arr[i]; i++;前面兩條語句可以寫成var a=arr[i++]

10. 使用直接量

JS效能優化38條"軍規",2019年嘔心力作

11. 使用DocumentFragment優化多次append

一旦需要更新DOM,請考慮使用文件碎片來構建DOM結構,然後再將其新增到現存的文件中。

JS效能優化38條"軍規",2019年嘔心力作

12. 使用一次innerHTML賦值代替構建dom元素

對於大的DOM更改,使用innerHTML要比使用標準的DOM方法建立同樣的DOM結構快得多。

JS效能優化38條"軍規",2019年嘔心力作

13. 通過模板元素clone,替代createElement

很多人喜歡在JavaScript中使用document.write來給頁面生成內容。事實上這樣的效率較低,如果需要直接插入HTML,可以找一個容器元素,比如指定一個div或者span,並設定他們的innerHTML來將自己的HTML程式碼插入到頁面中。通常我們可能會使用字串直接寫HTML來建立節點,其實這樣做,1無法保證程式碼的有效性2字串操作效率低,所以應該是用document.createElement()方法,而如果文件中存在現成的樣板節點,應該是用cloneNode()方法,因為使用createElement()方法之後,你需要設定多次元素的屬性,使用cloneNode()則可以減少屬性的設定次數——同樣如果需要建立很多元素,應該先準備一個樣板節點。

JS效能優化38條"軍規",2019年嘔心力作

14. 使用firstChild和nextSibling代替childNodes遍歷dom元素

JS效能優化38條"軍規",2019年嘔心力作

15. 刪除DOM節點

刪除dom節點之前,一定要刪除註冊在該節點上的事件,不管是用observe方式還是用attachEvent方式註冊的事件,否則將會產生無法回收的記憶體。另外,在removeChild和innerHTML=’’二者之間,儘量選擇後者. 因為在sIEve(記憶體洩露監測工具)中監測的結果是用removeChild無法有效地釋放dom節點。

16. 使用事件代理

任何可以冒泡的事件都不僅僅可以在事件目標上進行處理,目標的任何祖先節點上也能處理,使用這個知識就可以將事件處理程式附加到更高的地方負責多個目標的事件處理,同樣,對於內容動態增加並且子節點都需要相同的事件處理函式的情況,可以把事件註冊提到父節點上,這樣就不需要為每個子節點註冊事件監聽了。另外,現有的js庫都採用observe方式來建立事件監聽,其實現上隔離了dom物件和事件處理函式之間的迴圈引用,所以應該儘量採用這種方式來建立事件監聽。

17. 重複使用的呼叫結果,事先儲存到區域性變數

JS效能優化38條"軍規",2019年嘔心力作

18. 注意NodeList

最小化訪問NodeList的次數可以極大的改進指令碼的效能。

JS效能優化38條"軍規",2019年嘔心力作

編寫JavaScript的時候一定要知道何時返回NodeList物件,這樣可以最小化對它們的訪問,進行了對getElementsByTagName()的呼叫,獲取了元素的childNodes屬性,獲取了元素的attributes屬性,訪問了特殊的集合,如document.forms、document.images等等,要了解了當使用NodeList物件時,合理使用會極大的提升程式碼執行速度。

19. 優化迴圈

可以使用下面幾種方式來優化迴圈。

減值迭代

大多數迴圈使用一個從0開始、增加到某個特定值的迭代器,在很多情況下,從最大值開始,在迴圈中不斷減值的迭代器更加高效。

簡化終止條件

由於每次迴圈過程都會計算終止條件,所以必須保證它儘可能快,也就是說避免屬性查詢或者其它的操作,最好是將迴圈控制量儲存到區域性變數中,也就是說對陣列或列表物件的遍歷時,提前將length儲存到區域性變數中,避免在迴圈的每一步重複取值。

JS效能優化38條"軍規",2019年嘔心力作

簡化迴圈體

迴圈體是執行最多的,所以要確保其被最大限度的優化

使用後測試迴圈

在JavaScript中,我們可以使用for(;;),while(),for(in)三種迴圈,事實上,這三種迴圈中for(in)的效率極差,因為他需要查詢雜湊鍵,只要可以,就應該儘量少用。for(;;)和while迴圈,while迴圈的效率要優於for(;;),可能是因為for(;;)結構的問題,需要經常跳轉回去。

JS效能優化38條"軍規",2019年嘔心力作

最常用的for迴圈和while迴圈都是前測試迴圈,而如do-while這種後測試迴圈,可以避免最初終止條件的計算,因此執行更快。

20. 展開迴圈

當迴圈次數是確定的,消除迴圈並使用多次函式呼叫往往會更快。

21. 避免雙重解釋

儘量少使用eval函式

如果要提高程式碼效能,儘可能避免出現需要按照JavaScript解釋的字串,也就是儘量少使用eval函式, 使用eval相當於在執行時再次呼叫解釋引擎對內容進行執行,需要消耗大量時間,而且使用Eval帶來的安全性問題也是不容忽視的。

不要使用Function構造器

不要給setTimeout或者setInterval傳遞字串引數

JS效能優化38條"軍規",2019年嘔心力作

22. 縮短否定檢測

JS效能優化38條"軍規",2019年嘔心力作

23. 條件分支

將條件分支,按可能性順序從高到低排列:可以減少直譯器對條件的探測次數,在同一條件子的多(>2)條件分支時,使用switch優於if:switch分支選擇的效率高於if,在IE下尤為明顯。4分支的測試,IE下switch的執行時間約為if的一半,使用三目運算子替代條件分支。

JS效能優化38條"軍規",2019年嘔心力作

24. 使用常量

重複值:任何在多處用到的值都應該抽取為一個常量,使用者介面字串:任何用於顯示給使用者的字串,都應該抽取出來以方便國際化,URLs:在Web應用中,資源位置很容易變更,所以推薦用一個公共地方存放所有的URL,任意可能會更改的值:每當你用到字面量值的時候,你都要問一下自己這個值在未來是不是會變化,如果答案是“是”,那麼這個值就應該被提取出來作為一個常量。

25. 避免與null進行比較

由於JavaScript是弱型別的,所以它不會做任何的自動型別檢查,所以如果看到與null進行比較的程式碼,嘗試使用以下技術替換,如果值應為一個引用型別,使用instanceof操作符檢查其建構函式,如果值應為一個基本型別,作用typeof檢查其型別,如果是希望物件包含某個特定的方法名,則使用typeof操作符確保指定名字的方法存在於物件上。

26. 避免全域性量

全域性變數應該全部字母大寫,各單詞之間用_下劃線來連線。儘可能避免全域性變數和函式,儘量減少全域性變數的使用,因為在一個頁面中包含的所有JavaScript都在同一個域中執行。所以如果你的程式碼中宣告瞭全域性變數或者全域性函式的話,後面的程式碼中載入的指令碼檔案中的同名變數和函式會覆蓋掉(overwrite)你的。

JS效能優化38條"軍規",2019年嘔心力作

27. 尊重物件的所有權

因為JavaScript可以在任何時候修改任意物件,這樣就可以以不可預計的方式覆寫預設的行為,所以如果你不負責維護某個物件,它的物件或者它的方法,那麼你就不要對它進行修改,具體一點就是說:

  • 不要為例項或原型新增屬性

  • 不要為例項或者原型新增方法

  • 不要重定義已經存在的方法

  • 不要重複定義其它團隊成員已經實現的方法,永遠不要修改不是由你所有的物件,你可以通過以下方式為物件建立新的功能。

  • 建立包含所需功能的新物件,並用它與相關物件進行互動

  • 建立自定義型別,繼承需要進行修改的型別,然後可以為自定義型別新增額外功能

28. 迴圈引用

如果迴圈引用中包含DOM物件或者ActiveX物件,那麼就會發生記憶體洩露。記憶體洩露的後果是在瀏覽器關閉前,即使是重新整理頁面,這部分記憶體不會被瀏覽器釋放。簡單的迴圈引用:

JS效能優化38條"軍規",2019年嘔心力作

但是通常不會出現這種情況。通常迴圈引用發生在為dom元素新增閉包作為expendo的時候。

JS效能優化38條"軍規",2019年嘔心力作

init在執行的時候,當前上下文我們叫做context。這個時候,context引用了el,el引用了function,function引用了context。這時候形成了一個迴圈引用。

下面2種方法可以解決迴圈引用:

  • 置空dom物件

JS效能優化38條"軍規",2019年嘔心力作

將el置空,context中不包含對dom物件的引用,從而打斷迴圈應用。如果我們需要將dom物件返回,可以用如下方法:

JS效能優化38條"軍規",2019年嘔心力作

  • 構造新的context

JS效能優化38條"軍規",2019年嘔心力作

把function抽到新的context中,這樣,function的context就不包含對el的引用,從而打斷迴圈引用。

29. 通過javascript建立的dom物件,必須append到頁面中

IE下,指令碼建立的dom物件,如果沒有append到頁面中,重新整理頁面,這部分記憶體是不會回收的!

JS效能優化38條"軍規",2019年嘔心力作

30. 釋放dom元素佔用的記憶體

將dom元素的innerHTML設定為空字串,可以釋放其子元素佔用的記憶體。在rich應用中,使用者也許會在一個頁面上停留很長時間,可以使用該方法釋放積累得越來越多的dom元素使用的記憶體。

31. 釋放javascript物件

在rich應用中,隨著例項化物件數量的增加,記憶體消耗會越來越大。所以應當及時釋放對物件的引用,讓GC能夠回收這些記憶體控制元件。 物件:obj = null 物件屬性:delete obj.myproperty 陣列item:使用陣列的splice方法釋放陣列中不用的item

32. 避免string的隱式裝箱

對string的方法呼叫,比如'xxx'.length,瀏覽器會進行一個隱式的裝箱操作,將字串先轉換成一個String物件。推薦對宣告有可能使用String例項方法的字串時,採用如下寫法:

var myString = new String('Hello World');

33. 效能方面的注意事項

1. 儘量使用原生方法

2. switch語句相對if較快

通過將case語句按照最可能到最不可能的順序進行組織

3. 位運算較快

當進行數字運算時,位運算操作要比任何布林運算或者算數運算快

4. 巧用||和&&布林運算子

JS效能優化38條"軍規",2019年嘔心力作

34. 避免錯誤應注意的地方

每條語句末尾須加分號

在if語句中,即使條件表示式只有一條語句也要用{}把它括起來,以免後續如果新增了語句之後造成邏輯錯誤。

使用+號時需謹慎

JavaScript 和其他程式語言不同的是,在 JavaScript 中,'+'除了表示數字值相加,字串相連線以外,還可以作一元運算子用,把字串轉換為數字。因而如果使用不當,則可能與自增符'++'混淆而引起計算錯誤。

JS效能優化38條"軍規",2019年嘔心力作

使用return語句需要注意

一條有返回值的return語句不要用()括號來括住返回值,如果返回表示式,則表示式應與return關鍵字在同一行,以避免壓縮時,壓縮工具自動加分號而造成返回與開發人員不一致的結果。

JS效能優化38條"軍規",2019年嘔心力作

34. ==和===的區別

避免在if和while語句的條件部分進行賦值,如if (a = b),應該寫成if (a == b),但是在比較是否相等的情況下,最好使用全等執行符,也就是使用===和!==操作符會相對於==和!=會好點。==和!=操作符會進行型別強制轉換。

JS效能優化38條"軍規",2019年嘔心力作

35. 不要使用生偏語法

不要使用生偏語法,寫讓人迷惑的程式碼,雖然計算機能夠正確識別並執行,但是晦澀難懂的程式碼不方便以後維護。

36. 函式返回統一型別

雖然JavaScript是弱型別的,對於函式來說,前面返回整數型資料,後面返回布林值在編譯和執行都可以正常通過,但為了規範和以後維護時容易理解,應保證函式應返回統一的資料型別。

37. 何時用單引號,何時用雙引號

雖然在JavaScript當中,雙引號和單引號都可以表示字串, 為了避免混亂,我們建議在HTML中使用雙引號,在JavaScript中使用單引號,但為了相容各個瀏覽器,也為了解析時不會出錯,定義JSON物件時,最好使用雙引號。

38. 總是檢查資料型別

要檢查你的方法輸入的所有資料,一方面是為了安全性,另一方面也是為了可用性。使用者隨時隨地都會輸入錯誤的資料。這不是因為他們蠢,而是因為他們很忙,並且思考的方式跟你不同。用typeof方法來檢測你的function接受的輸入是否合法。

39. 結束語

  • 永遠不要忽略程式碼優化工作,重構是一項從專案開始到結束需要持續的工作,只有不斷的優化程式碼才能讓程式碼的執行效率越來越好!

40. 我的公開課

最近加班加點錄製了一套JS系列視訊,涵蓋了JS基礎、JQ基礎、JS高階、JS實戰四個部分,大家可以報名學習下,都是免費的,後續我還會錄製一套MVVM系列的視訊,報名下面的課程後,大家也可以加我微信,有什麼前端技術問題,都可以隨時跟我討論的。

JS系列視訊教程地址點選學習>>>

我的微信(訊息備註"掘金",謝謝!):

JS效能優化38條"軍規",2019年嘔心力作

相關文章