如何從效能角度選擇陣列的遍歷方式

前端南玖發表於2021-11-23

前言

本文講述了JS常用的幾種陣列遍歷方式以及效能分析對比。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新的文章~

陣列的方法

JavaScript發展到現在已經提供了許多陣列的方法,下面這張圖涵蓋了陣列大部分的方法,這篇文章主要說一說陣列的遍歷方法,以及各自的效能,方法這麼多,如何挑選效能最佳的方法對我們的開發有非常大的幫助。

陣列遍歷的方法

for

標準的for迴圈語句,也是最傳統的迴圈語句

var arr = [1,2,3,4,5]
for(var i=0;i<arr.length;i++){
  console.log(arr[i])
}

最簡單的一種遍歷方式,也是使用頻率最高的,效能較好,但還能優化

優化版for迴圈語句

var arr = [1,2,3,4,5]
for(var i=0,len=arr.length;i<len;i++){
  console.log(arr[i])
}

使用臨時變數,將長度快取起來,避免重複獲取陣列長度,尤其是當陣列長度較大時優化效果才會更加明顯。

這種方法基本上是所有迴圈遍歷方法中效能最高的一種

forEach

普通forEach

對陣列中的每一元素執行給定的函式,沒有返回值,常用來遍歷元素

var arr5 = [10,20,30]
var result5 = arr5.forEach((item,index,arr)=>{
    console.log(item)
})
console.log(result5)
/*
10
20
30
undefined   該方法沒有返回值
*/

陣列自帶的foreach迴圈,使用頻率較高,實際上效能比普通for迴圈弱

原型forEach

由於foreach是Array型自帶的,對於一些非這種型別的,無法直接使用(如NodeList),所以才有了這個變種,使用這個變種可以讓類似的陣列擁有foreach功能。

const nodes = document.querySelectorAll('div')
Array.prototype.forEach.call(nodes,(item,index,arr)=>{
  console.log(item)
})

實際效能要比普通foreach弱

for...in

任意順序遍歷一個物件的除Symbol以外的可列舉屬性,包括繼承的可列舉屬性。

一般常用來遍歷物件,包括非整數型別的名稱和繼承的那些原型鏈上面的屬性也能被遍歷。像 Array和 Object使用內建建構函式所建立的物件都會繼承自Object.prototype和String.prototype的不可列舉屬性就不能遍歷了.

var arr = [1,2,3,4,5]
for(var i in arr){
  console.log(i,arr[i])
}  //這裡的i是物件屬性,也就是陣列的下標
/**
0 1
1 2
2 3
3 4
4 5 **/

大部分人都喜歡用這個方法,但它的效能卻不怎麼好

for...of(不能遍歷物件)

在可迭代物件(具有 iterator 介面)(Array,Map,Set,String,arguments)上建立一個迭代迴圈,呼叫自定義迭代鉤子,併為每個不同屬性的值執行語句,不能遍歷物件

let arr=["前端","南玖","ssss"];
    for (let item of arr){
        console.log(item)
    }
//前端 南玖 ssss

//遍歷物件
let person={name:"南玖",age:18,city:"上海"}
for (let item of person){
  console.log(item)
}
// 我們發現它是不可以的 我們可以搭配Object.keys使用
for(let item of Object.keys(person)){
    console.log(person[item])
}
// 南玖 18 上海

這種方式是es6裡面用到的,效能要好於forin,但仍然比不上普通for迴圈

map

map: 只能遍歷陣列,不能中斷,返回值是修改後的陣列。

let arr=[1,2,3];
const res = arr.map(item=>{
  return res+1
})
console.log(res) //[2,3,4]
console.log(arr) // [1,2,3]

every

對陣列中的每一執行給定的函式,如果該函式對每一項都返回true,則該函式返回true

var arr = [10,30,25,64,18,3,9]
var result = arr.every((item,index,arr)=>{
      return item>3
})
console.log(result)  //false

some

對陣列中的每一執行給定的函式,如果該函式有一項返回true,就返回true,所有項返回false才返回false

var arr2 = [10,20,32,45,36,94,75]
var result2 = arr2.some((item,index,arr)=>{
    return item<10
})
console.log(result2)  //false

reduce

reduce()方法對陣列中的每個元素執行一個由你提供的reducer函式(升序執行),將其結果彙總為單個返回值

const array = [1,2,3,4]
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));

filter

對陣列中的每一執行給定的函式,會返回滿足該函式的項組成的陣列

// filter  返回滿足要求的陣列項組成的新陣列
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
    return item > 3
})
console.log(result3)  //[6,7,12,20,64,35]

效能測試

工具測試

使用工具測試效能分析結果如下圖所示

手動測試

我們也可以自己用程式碼測試:

//測試函式
function clecTime(fn,fnName){
        const start = new Date().getTime()
        if(fn) fn()
        const end = new Date().getTime()
        console.log(`${fnName}執行耗時:${end-start}ms`)
}

function forfn(){
  let a = []
  for(var i=0;i<arr.length;i++){
    // console.log(i)
    a.push(arr[i])
  }
}
clecTime(forfn, 'for')   //for執行耗時:106ms

function forlenfn(){
  let a = []
  for(var i=0,len=arr.length;i<len;i++){
    a.push(arr[i])
  }
}
clecTime(forlenfn, 'for len')   //for len執行耗時:95ms

function forEachfn(){
  let a = []
  arr.forEach(item=>{
    a.push[item]
  })
}
clecTime(forEachfn, 'forEach')   //forEach執行耗時:201ms

function forinfn(){
  let a = []
  for(var i in arr){
    a.push(arr[i])
  }
}
clecTime(forinfn, 'forin') //forin執行耗時:2584ms (離譜)

function foroffn(){
  let a = []
  for(var i of arr){
    a.push(i)
  }
}
clecTime(foroffn, 'forof') //forof執行耗時:221ms

//  ...其餘可自行測試

結果分析

經過工具與手動測試發現,結果基本一致,陣列遍歷各個方法的速度:傳統的for迴圈最快,for-in最慢

for-len > for > for-of > forEach > map > for-in

javascript原生遍歷方法的建議用法:

  • for迴圈遍歷陣列
  • for...in遍歷物件
  • for...of遍歷類陣列物件(ES6)
  • Object.keys()獲取物件屬性名的集合

為何for… in會慢?

因為for … in語法是第一個能夠迭代物件鍵的JavaScript語句,迴圈物件鍵({})與在陣列([])上進行迴圈不同,引擎會執行一些額外的工作來跟蹤已經迭代的屬性。因此不建議使用for...in來遍歷陣列

推薦閱讀

這些瀏覽器面試題,看看你能回答幾個?
這一次帶你徹底瞭解前端本地儲存
面試官:說一說前端路由與後端路由的區別
JavaScript之原型與原型鏈
Javascript深入之作用域與閉包
this指向與call,apply,bind

覺得文章不錯,可以點個贊呀_ 另外歡迎關注留言交流~

相關文章