JS數組基本操做——數組遍歷到底有多少種方式?

源於一次面試,一塊兒面試的同事問面試者的一個問題:數組遍歷有哪些方式?想來數組操做是平時開發中的經常使用技能,面試者吞吞吐吐大概就說出了兩種方式吧,最後就淘汰掉啦(面試者是個很認真的妹紙,面試都在簡單作一些筆記,不過基礎確實有些困難~)。node

對於"數組遍歷"這個問題,其實答案很寬泛,關鍵在於你能不能列舉出必定數量的方法以及描述它們之間的區別。本文即介紹一下數組的基本遍歷操做和高階函數。python

1、數組基本遍歷

本部分介紹4種最經常使用的遍歷方式。面試

1.for...in

for...in實際上是對象的遍歷方式,並非數組專有,使用for...in將循環遍歷對象自己的全部可枚舉屬性,以及對象從其構造函數原型中繼承的屬性,其遍歷順序與Object.keys()函數取到的列表一致。數組

該方法會遍歷數組中非數字下標的元素,會忽略空元素:瀏覽器

let list = [7, 5, 2, 3]
list[10] = 1
list['a'] = 1
console.log(JSON.stringify(Object.keys(list)))

for (let key in list) {
  console.log(key, list[key])
}

輸出:函數

> ["0","1","2","3","10","a"]
> 0, 7
> 1, 5
> 2, 2
> 3, 3
> 10, 1
> a, 1

這個方法遍歷數組是最坑的,它一般表現爲有序,可是由於它是按照對象的枚舉順序來遍歷的,也就是規範沒有規定順序的,因此具體實現是由着瀏覽器來的。MDN文檔裏也明確建議「不要依賴其遍歷順序」:
image.pngpost

2.for...of

這個方法用於可迭代對象的迭代,用來遍歷數組是有序的,而且迭代的是數組的值。該方法不會遍歷非數字下標的元素,同時不會忽略數組的空元素:網站

let list = [7, 5, 2, 3]
list[5] = 4
list[4] = 5
list[10] = 1
// 此時下標六、七、八、9爲空元素
list['a'] = 'a'

for (let value of list) {
  console.log(value)
}

輸出:this

> 7
> 5
> 2
> 3
> 5
> 4
>   // 遍歷空元素
>  // 遍歷空元素
>  // 遍歷空元素
>  // 遍歷空元素
> 1

3.取數組長度進行遍歷

該方法和方法2比較像,是有序的,不會忽略空元素。spa

let list = ['a', 'b', 'c', 'd']
list[4] = 'e'
list[10] = 'z'
list['a'] = 0

for (let idx = 0; idx < list.length; idx++) {
  console.log(idx, list[idx])
}

輸出:

> 0, a
> 1, b
> 2, c
> 3, d
> 4, e
> 5, //空元素
> 6, 
> 7, 
> 8, 
> 9, 
> 10, z

4.forEach遍歷

forEach是數組的一個高階函數,用法以下:

arr.forEach(callback[, thisArg])

參數說明:

callback
爲數組中每一個元素執行的函數,該函數接收三個參數:

  • currentValue

數組中正在處理的當前元素。

  • index 可選

數組中正在處理的當前元素的索引。

  • array 可選

forEach() 方法正在操做的數組。

thisArg可選
可選參數。當執行回調函數時用做 this 的值(參考對象)。

forEach遍歷數組會按照數組下標升序遍歷,而且會忽略空元素:

let list = ['a', 'b', 'c', 'd']
list[4] = 'e'
list[10] = 'z'
list['a'] = 0

list.forEach((value, key, list) => {
  console.log(key, value)
})

輸出:

> 0, a
> 1, b
> 2, c
> 3, d
> 4, e
> 10, z

有一個很容易忽略的細節,咱們都應該儘量地避免在遍歷中取增刪數組的元素,不然會出現一些意外的狀況,而且不一樣的遍歷方法還會有不一樣的表現。

for...of和forEach遍歷中刪除元素

好比for...of遍歷中刪除元素:

let list = ['a', 'b', 'c', 'd']

for (let item of list) {
  if (item === 'a') {
    list.splice(0, 1)
  }
  console.log(item)
}

輸出:

> a
> c
> d

forEach遍歷中刪除元素:

let list = ['a', 'b', 'c', 'd']

list.forEach((item, idx) => {
  if (item === 'a') {
    list.splice(0, 1)
  }
  console.log(item)
})

輸出:

> a
> c
> d

能夠看到,兩者表現一致,遍歷到a的時候,把a刪除,則b會被跳過,增長元素則略爲不一樣。

for…of和forEach遍歷中增長元素

for...of遍歷中增長元素:

let list = ['a', 'b', 'c', 'd']
for (let item of list) {
  if (item === 'a') {
    list.splice(1, 0, 'e')
  }
  console.log(item)
}

輸出:

> a
> e
> b
> c
> d

forEach遍歷中增長元素:

let list = ['a', 'b', 'c', 'd']

list.forEach((item, idx) => {
  if (item === 'a') {
    list.splice(1, 0, 'e')
  }
  console.log(item)
})

輸出:

> a
> e
> b
> c

咦,少了個'd'! 能夠看到,其實forEach遍歷次數在一開始就已肯定,因此最後的'd'沒有輸出出來,這是forEach和for遍歷數組的一個區別,另外一個重要區別是forEach不可用break, continue, return等中斷循環,而for則能夠。

總之,在遍歷數組過程當中,對數組的操做要很是當心,這一點python、js很類似,由於兩門語言中,對象/字典和數組都是引用,都爲可變對象。

2、利用高階函數遍歷數組

上面介紹的4種算是比較標準的遍歷方式,不過JS中數組還有不少的高階函數,這些函數其實均可以達到遍歷數組的目的,只不過每一個函數的應用場景不一樣,下面簡單介紹一下。

1. map

map() 方法參數與forEach徹底相同,兩者區別僅僅在於map會將回調函數的返回值收集起來產生一個新數組。
好比將數組中每一個元素的2倍輸出爲一個新數組:

let list = [1, 2, 3, 4]
let result = list.map((value, idx) => value * 2)
console.log(result) // 輸出[2,4,6,8]

2.filter

filter() 參數與forEach徹底一致,不過它的callback函數應該返回一個真值或假值。filter() 方法建立一個新數組, 新數組包含全部使得callback返回值爲真值(Truthy,與true有區別)的元素。
好比過濾數組中的偶數:

let list = [1, 2, 3, 4]
let result = list.filter((value, idx) => value % 2 === 0)
console.log(result) // 輸出[2,4]

3. find/findIndex

find() 方法返回數組中使callback返回值爲Truthy的第一個元素的值,沒有則返回undefined。使用很是簡單,好比找出數組中第一個偶數:

let list = ['1', '2', '3', '4']
let result = list.find(value => value % 2 === 0)
console.log(result) // 輸出 2

findIndex()方法與find方法很相似,只不過findIndex返回使callback返回值爲Truthy的第一個元素的索引,沒有符合元素則返回-1。好比找出數組中第一個偶數的下標:

let list = [1, 2, 3, 4]
let result = list.findIndex(value => value % 2 === 0)
console.log(result) // 輸出 1

4.every/some

兩個函數接收參數都與以上函數相同,返回都是布爾值。every用於判斷是否數組中每一項都使得callback返回值爲Truthy,some用於判斷是否至少存在一項使得callback元素返回值爲Truthy。

let list = [1, 2, 3, 4]
// 判斷數組中是否每一個元素小於10
let result = list.every(value => {
  return value < 10
})
console.log(result) // 輸出true

// 判斷是否每一個元素大於2
result = list.every(value => {
  return value > 2
})
console.log(result) // 輸出false

// 判斷是數組中否存在1
result = list.some(value => {
  return value === 1
})
console.log(result) // 輸出true

// 判斷數組中是否存在大於10的數
result = list.some(value => {
  return value > 10
})
console.log(result) // 輸出false

5.reduce/reduceRight 累加器

參數與其它函數有所不一樣:
callback
執行數組中每一個值的函數,包含四個參數:

  • accumulator

累計器累計回調的返回值; 它是上一次調用回調時返回的累積值,或initialValue(見於下方)。

  • currentValue

數組中正在處理的元素。

  • currentIndex 可選

數組中正在處理的當前元素的索引。 若是提供了initialValue,則起始索引號爲0,不然爲1。

  • array 可選

調用reduce()的數組

initialValue可選
做爲第一次調用 callback函數時的第一個參數的值。 若是沒有提供初始值,則將使用數組中的第一個元素。 在沒有初始值的空數組上調用 reduce 將報錯。

reduce() 方法對數組中的每一個元素執行一個由您提供的reducer函數(升序執行),將其結果彙總爲單個返回值,而reduceRight只是遍歷順序相反而已。

好比很常見的一個需求是,把一個以下結構的list變成一個樹形結構,使用forEach和reduce能夠輕鬆實現。
列表結構:

let list = [
  {
    id: 1,
    parentId: ''
  },
  {
      id: 2,
      parentId: ''
  },
  {
      id: 3,
      parentId: 1
  },
  {
      id: 4,
      parentId: 2,
  },
  {
      id: 5,
    parentId: 3
  },
  {
      id: 6,
    parentId: 3
  }
]

樹形結構:

[
    {
        "id":1,
        "parentId":"",
        "children":[
            {
                "id":3,
                "parentId":1,
                "children":[
                    {
                        "id":5,
                        "parentId":3
                    },
                    {
                        "id":6,
                        "parentId":3
                    }
                ]
            }
        ]
    },
    {
        "id":2,
        "parentId":"",
        "children":[
            {
                "id":4,
                "parentId":2
            }
        ]
    }
]

利用reduce和forEach實現list轉爲樹形結構:

function listToTree(srcList) {
  let result = []
  // reduce收集全部節點信息存放在對象中,能夠用forEach改寫,不過代碼會多幾行
  let nodeInfo = list.reduce((data, node) => (data[node.id] = node, data), {})

 // forEach給全部元素找媽媽
  srcList.forEach(node => {
    if (!node.parentId) {
      result.push(node)
      return
    }
    let parent = nodeInfo[node.parentId]
    parent.children = parent.children || []
    parent.children.push(node)
  })
  return result
}

以上即爲本文圍繞數組遍歷介紹的數組基本操做。這些高階函數其實均可以用於數組遍歷(若是想強行遍歷的話,好比some的callback恆返回false),不過實際使用中應該根據不一樣的需求選用不一樣的方法。

至此,面試中遇到「數組遍歷有多少種方法?」這種問題,你能夠回答「10種以上」了,畢竟,本文介紹了12種...


最後,JS實際上是一門特別愚蠢的語言,有時候你交給它的事情,它不會辦不說,居然還會罵人!不信?控制檯輸入下面的算式試試:

(![]+{})[-~!+[]^-~[]]+([]+{})[-~!![]]

Just for fun. 別太認真~,~


本文原創,首發於個人我的網站:http://wintc.top/site/article?postId=8,轉載請註明出處。

相關文章
相關標籤/搜索