簡單總結了10個JavaScript程式碼優化小tips

語言: CN / TW / HK

theme: fancy highlight: atom-one-light


持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第25天,點選檢視活動詳情

Hi~,我是一碗周,如果寫的文章有幸可以得到你的青睞,萬分有幸~

🍏 寫在前面

想要做到JavaScript的程式碼優化,首先需要做的是準確的測試JavaScript的程式碼執行時間。其實需要做的就是採集大量的執行樣本進行數學統計和分析,這裡我們使用的是benchmark.js來檢測程式碼的執行情況。

首先我們需要在專案中安裝依賴,程式碼如下:

```powershell yarn add benchmark --save

或者

npm i benchmark --save ```

然後我們寫一個測試程式碼,如下所示:

```javascript const Benchmark = require('benchmark')

const suite = new Benchmark.Suite()

// 新增測試 suite /* * add() 方法接受兩個引數,其中第一個表示測試的名稱,第二個表示測試的內容,他是一個函式
/ .add('join1000', () => { new Array(1000).join(' ') }) .add('join10000', () => { new Array(10000).join(' ') }) // 新增時間監聽 .on('cycle', event => { // 列印執行時間 console.log(String(event.target)) }) // 完成後執行觸發的事件 .on('complete', () => { console.log('最快的是:' + suite.filter('fastest').map('name')) }) // 執行測試 .run({ async: true })

```

程式碼執行結果如下:

純文字 // join1000 x 146,854 ops/sec ±1.86% (88 runs sampled) // join10000 x 16,083 ops/sec ±1.06% (92 runs sampled) // 最快的是:join1000

在結果中,ops/sec表示的是每秒執行的次數,當然是越大越好,緊接著是每秒執行次數上下相差的百分比,最後括號中的內容表示共取樣多少次。

或者也可以使用JSBench.me工具進行替換,網站測試截圖如下:

01_JSBench.Me測試_Ubj550U0gB.png

我們可以看到,都是join1000的效能更好一些(我感覺我在說廢話)。

🍑 慎用全域性變數

這裡所說的慎用全域性變數,為什麼要慎用呢?主要有以下幾點:

全域性變數定義在全域性執行上下文,是所有作用域鏈的頂端。每次查詢的時候都從區域性找到最頂端,在時間上會有所消耗。

全域性執行上下文一直存在於上下文的執行棧,直到程式退出,才會被銷燬,記憶體空間浪費

如果某個區域性作用域出現了同名的變數則會遮蓋或者說汙染全域性變數 。

下面我們就來寫一段程式碼,看一下全域性變數與佈局變數在執行效率方面的差異,程式碼如下:

```javascript ... suite .add('全域性變數', () => { // 該函式內模擬全域性作用域 let i, str = '' for (i = 0; i < 1000; i++) { str += i } }) .add('區域性變數', () => { for (let i = 0, str = ''; i < 1000; i++) { str += i } }) ...

```

程式碼執行結果如下:

純文字 全域性變數 x 158,697 ops/sec ±1.05% (87 runs sampled) 區域性變數 x 160,697 ops/sec ±1.03% (90 runs sampled) 最快的是:區域性變數

雖然說差異不大,但是我們可以感知全域性變數比區域性的效能更差一些。

🍒 通過原型新增方法

為建構函式增加例項物件需要的方法時,儘量使用原型的方式新增,而不是建構函式內部進行新增,我們可以看如下測試程式碼:

javascript ... suite .add('建構函式內部新增', () => { function Person() { this.sayMe = function () { return '一碗周' } } let p = new Person() }) .add('原型方式內部新增', () => { function Person() {} Person.prototype.sayMe = function () { return '一碗周' } let p = new Person() }) ...

程式碼執行結果如下:

純文字 建構函式內部新增 x 573,786 ops/sec ±1.97% (89 runs sampled) 原型方式內部新增 x 581,693 ops/sec ±3.46% (80 runs sampled) 最快的是:建構函式內部新增

🍓 避免閉包中的記憶體洩露

由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,嚴重可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除 (即將區域性變數重新賦值為null)。

🫐 避免使用屬性訪問方法

在JavaScript中的物件中,避免使用一些屬性訪問方法,這是因為JavaScript中的所有屬性都是外部可見的。

示例程式碼如下:

javascript ... suite .add('使用屬性訪問方法', () => { function Person() { this.name = '一碗周' this.getName = function () { return '一碗周' } } let p = new Person() let n = p.getName() }) .add('不使用屬性訪問方法', () => { function Person() { this.name = '一碗周' } let p = new Person() let n = p.name }) ...

程式碼執行結果如下:

純文字 使用屬性訪問方法 x 406,682 ops/sec ±2.33% (82 runs sampled) 不使用屬性訪問方法 x 554,169 ops/sec ±2.03% (85 runs sampled) 最快的是:不使用屬性訪問方法

🍊 for迴圈優化

我們在使用for迴圈時,可以將有些必要的資料進行快取,就比如arr.length這種屬性,不需要每次判斷都獲取一下,從而優化我們的程式碼。

示例程式碼如下:

javascript ... suite .add('正序', () => { let arr = new Array(100) let str = '' for (let i = 0; i < arr.length; i++) { str += i } }) .add('快取', () => { let arr = new Array(100) let str = '' for (let i = arr.length; i; i--) { str += i } }) .add('快取的另一種寫法', () => { let arr = new Array(100) let str = '' for (let i = 0, l = arr.length; i < l; i++) { str += i } }) ...

程式碼執行結果如下:

```純文字 正序 x 1,322,889 ops/sec ±1.36% (86 runs sampled) 快取 x 1,356,696 ops/sec ±0.70% (92 runs sampled) 快取的另一種寫法 x 1,383,091 ops/sec ±0.70% (93 runs sampled) 最快的是:快取的另一種寫法

```

🍉 選擇最優的迴圈方式

我們現在常用的迴圈有forEachforfor...in迴圈,這幾種那個是效能最優的呢,測試程式碼如下:

javascript ... suite .add('forEach', () => { let arr = new Array(100) let str = '' arr.forEach(i => { str += i }) }) .add('for...in', () => { let arr = new Array(100) let str = '' for (i in arr) { str += i } }) .add('for', () => { let arr = new Array(100) let str = '' for (let i = 0, l = arr.length; i < l; i++) { str += i } }) ...

程式碼執行結果如下:

```純文字 forEach x 4,248,577 ops/sec ±0.89% (86 runs sampled) for...in x 4,583,375 ops/sec ±1.15% (91 runs sampled) for x 1,343,871 ops/sec ±1.91% (88 runs sampled) 最快的是:for...in

```

由執行結果可以看出我們可以儘量使用for...in或者forEach迴圈,減少使用for迴圈。

🍋 減少判斷層級

減少判斷層級就是減少一些if語句的巢狀,如果是一些必要的條件我們可以通過單層if結合return直接跳出函式的執行,關於優化前與優化後的程式碼執行比對如下所示:

```javascript ... /* 接收兩類檔案,zip 和 rar 壓縮包的大小限制為 10 兆 /

suite .add('巢狀寫法', () => { function uploadFile(suffix, size) { // 允許上傳的字尾名 const suffixList = ['.zip', '.rar'] const M = 1024* 1024

  if (suffixList.includes(suffix)) {
    if (size <= 10*  M) {
      return '下載成功'
    }
  }
}
uploadFile('.zip', 1*  1024*  1024)

}) .add('減少判斷寫法', () => { function uploadFile(suffix, size) { // 允許上傳的字尾名 const suffixList = ['.zip', '.rar'] const M = 1024 1024 if (!suffixList.includes(suffix)) return if (size > 10 M) return return '下載成功' } uploadFile('.zip', 1 1024 1024) }) ... ```

程式碼執行結果如下:

純文字 巢狀寫法 x 888,445,014 ops/sec ±2.48% (88 runs sampled) 減少判斷寫法 x 905,763,884 ops/sec ±1.35% (92 runs sampled) 最快的是:減少判斷寫法,巢狀寫法

雖然說差距並不是很大,但是不適用巢狀的程式碼比普通程式碼更優一些。

🍌 減少作用域鏈查詢層級

減少程式碼中作用域鏈的查詢也是程式碼優化的一種方法,如下程式碼展示了兩者的區別:

javascript ... suite .add('before', () => { var name = '一碗粥' function sayMe() { name = '一碗周' function print() { var age = 18 return name + age } print() } sayMe() }) .add('after', () => { var name = '一碗粥' function sayMe() { var name = '一碗周' // 形成區域性作用域 function print() { var age = 18 return name + age } print() } sayMe() }) ...

程式碼執行結果如下:

純文字 before x 15,509,793 ops/sec ±7.78% (76 runs sampled) after x 17,930,066 ops/sec ±2.89% (83 runs sampled) 最快的是:after

上面程式碼只是為了展示區別,並沒有實際意義。

🍍 減少資料讀取次數

如果物件中的某個資料在一個程式碼塊中使用兩遍以上,這樣的話將其進行快取從而減少資料的讀取次數來達到更優的一個性能,測試程式碼如下:

```javascript ... var userList = { one: { name: '一碗周', age: 18, }, two: { name: '一碗粥', age: 18, }, }

suite .add('before', () => { function returnOneInfo() { userList.one.info = userList.one.name + userList.one.age } returnOneInfo() }) .add('after', () => { function returnOneInfo() { let one = userList.one one.info = one.name + one.age } returnOneInfo() }) ... ```

程式碼執行結果如下:

純文字 before x 222,553,199 ops/sec ±16.63% (26 runs sampled) after x 177,894,903 ops/sec ±1.85% (88 runs sampled) 最快的是:before

🥭 字面量與構造式

凡是可以使用字面量方式宣告的內容,絕對是不可以使用建構函式的方式宣告的,兩者在效能方面相差甚遠,程式碼如下:

javascript ... suite .add('before', () => { var str = new String('string') }) .add('after', () => { var str = 'string' }) ...

程式碼執行結果如下:

```純文字 before x 38,601,223 ops/sec ±1.16% (89 runs sampled) after x 897,491,903 ops/sec ±0.92% (92 runs sampled) 最快的是:after

```

🍎 寫在最後

本篇文章總結了JavaScript中10個優化程式碼的小tips,如果對你有所幫助可以點贊支援一下\~