JS數(shù)組基本操作——數(shù)組遍歷到底有多少種方式?

原文鏈接:http://wintc.top/site/article?postId=8

對(duì)于"數(shù)組遍歷"這個(gè)問(wèn)題,其實(shí)答案很寬泛,關(guān)鍵在于你能不能列舉出一定數(shù)量的方法以及描述它們之間的區(qū)別。本文即介紹一下數(shù)組的基本遍歷操作和高階函數(shù)。

一、數(shù)組基本遍歷

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

1.for...in

for...in其實(shí)是對(duì)象的遍歷方式,并不是數(shù)組專(zhuān)有,使用for...in將循環(huán)遍歷對(duì)象本身的所有可枚舉屬性,以及對(duì)象從其構(gòu)造函數(shù)原型中繼承的屬性,其遍歷順序與Object.keys()函數(shù)取到的列表一致。

該方法會(huì)遍歷數(shù)組中非數(shù)字下標(biāo)的元素,會(huì)忽略空元素:

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

這個(gè)方法遍歷數(shù)組是最坑的,它通常表現(xiàn)為有序,但是因?yàn)樗前凑諏?duì)象的枚舉順序來(lái)遍歷的,也就是規(guī)范沒(méi)有規(guī)定順序的,所以具體實(shí)現(xiàn)是由著瀏覽器來(lái)的。MDN文檔里也明確建議“不要依賴(lài)其遍歷順序”:


image.png

2.for...of

這個(gè)方法用于可迭代對(duì)象的迭代,用來(lái)遍歷數(shù)組是有序的,并且迭代的是數(shù)組的值。該方法不會(huì)遍歷非數(shù)字下標(biāo)的元素,同時(shí)不會(huì)忽略數(shù)組的空元素:

let list = [7, 5, 2, 3]
list[5] = 4
list[4] = 5
list[10] = 1
// 此時(shí)下標(biāo)6、7、8、9為空元素
list['a'] = 'a'

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

輸出:

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

3.取數(shù)組長(zhǎng)度進(jìn)行遍歷

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

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是數(shù)組的一個(gè)高階函數(shù),用法如下:

arr.forEach(callback[, thisArg])

參數(shù)說(shuō)明:

callback
為數(shù)組中每個(gè)元素執(zhí)行的函數(shù),該函數(shù)接收三個(gè)參數(shù):

  • currentValue

數(shù)組中正在處理的當(dāng)前元素。

  • index 可選

數(shù)組中正在處理的當(dāng)前元素的索引。

  • array 可選

forEach() 方法正在操作的數(shù)組。

thisArg可選
可選參數(shù)。當(dāng)執(zhí)行回調(diào)函數(shù)時(shí)用作 this 的值(參考對(duì)象)。

forEach遍歷數(shù)組會(huì)按照數(shù)組下標(biāo)升序遍歷,并且會(huì)忽略空元素:

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

有一個(gè)很容易忽略的細(xì)節(jié),我們都應(yīng)該盡可能地避免在遍歷中取增刪數(shù)組的元素,否則會(huì)出現(xiàn)一些意外的情況,并且不同的遍歷方法還會(huì)有不同的表現(xiàn)。

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

可以看到,二者表現(xiàn)一致,遍歷到a的時(shí)候,把a刪除,則b會(huì)被跳過(guò),增加元素則略為不同。

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

咦,少了個(gè)'d'! 可以看到,其實(shí)forEach遍歷次數(shù)在一開(kāi)始就已確定,所以最后的'd'沒(méi)有輸出出來(lái),這是forEach和for遍歷數(shù)組的一個(gè)區(qū)別,另一個(gè)重要區(qū)別是forEach不可用break, continue, return等中斷循環(huán),而for則可以。

總之,在遍歷數(shù)組過(guò)程中,對(duì)數(shù)組的操作要非常小心,這一點(diǎn)python、js很相似,因?yàn)閮砷T(mén)語(yǔ)言中,對(duì)象/字典和數(shù)組都是引用,都為可變對(duì)象。

二、利用高階函數(shù)遍歷數(shù)組

上面介紹的4種算是比較標(biāo)準(zhǔn)的遍歷方式,不過(guò)JS中數(shù)組還有很多的高階函數(shù),這些函數(shù)其實(shí)都可以達(dá)到遍歷數(shù)組的目的,只不過(guò)每個(gè)函數(shù)的應(yīng)用場(chǎng)景不同,下面簡(jiǎn)單介紹一下。

1. map

map() 方法參數(shù)與forEach完全相同,二者區(qū)別僅僅在于map會(huì)將回調(diào)函數(shù)的返回值收集起來(lái)產(chǎn)生一個(gè)新數(shù)組。
比如將數(shù)組中每個(gè)元素的2倍輸出為一個(gè)新數(shù)組:

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

2.filter

filter() 參數(shù)與forEach完全一致,不過(guò)它的callback函數(shù)應(yīng)該返回一個(gè)真值或假值。filter() 方法創(chuàng)建一個(gè)新數(shù)組, 新數(shù)組包含所有使得callback返回值為真值(Truthy,與true有區(qū)別)的元素。
比如過(guò)濾數(shù)組中的偶數(shù):

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

3. find/findIndex

find() 方法返回?cái)?shù)組中使callback返回值為T(mén)ruthy的第一個(gè)元素的值,沒(méi)有則返回undefined。使用非常簡(jiǎn)單,比如找出數(shù)組中第一個(gè)偶數(shù):

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

findIndex()方法與find方法很類(lèi)似,只不過(guò)findIndex返回使callback返回值為T(mén)ruthy的第一個(gè)元素的索引,沒(méi)有符合元素則返回-1。比如找出數(shù)組中第一個(gè)偶數(shù)的下標(biāo):

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

4.every/some

兩個(gè)函數(shù)接收參數(shù)都與以上函數(shù)相同,返回都是布爾值。every用于判斷是否數(shù)組中每一項(xiàng)都使得callback返回值為T(mén)ruthy,some用于判斷是否至少存在一項(xiàng)使得callback元素返回值為T(mén)ruthy。

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

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

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

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

5.reduce/reduceRight 累加器

參數(shù)與其它函數(shù)有所不同:
callback
執(zhí)行數(shù)組中每個(gè)值的函數(shù),包含四個(gè)參數(shù):

  • accumulator

累計(jì)器累計(jì)回調(diào)的返回值; 它是上一次調(diào)用回調(diào)時(shí)返回的累積值,或initialValue(見(jiàn)于下方)。

  • currentValue

數(shù)組中正在處理的元素。

  • currentIndex 可選

數(shù)組中正在處理的當(dāng)前元素的索引。 如果提供了initialValue,則起始索引號(hào)為0,否則為1。

  • array 可選

調(diào)用reduce()的數(shù)組

initialValue可選
作為第一次調(diào)用 callback函數(shù)時(shí)的第一個(gè)參數(shù)的值。 如果沒(méi)有提供初始值,則將使用數(shù)組中的第一個(gè)元素。 在沒(méi)有初始值的空數(shù)組上調(diào)用 reduce 將報(bào)錯(cuò)。

reduce() 方法對(duì)數(shù)組中的每個(gè)元素執(zhí)行一個(gè)由您提供的reducer函數(shù)(升序執(zhí)行),將其結(jié)果匯總為單個(gè)返回值,而reduceRight只是遍歷順序相反而已。

比如很常見(jiàn)的一個(gè)需求是,把一個(gè)如下結(jié)構(gòu)的list變成一個(gè)樹(shù)形結(jié)構(gòu),使用forEach和reduce可以輕松實(shí)現(xiàn)。

列表結(jié)構(gòu):

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

樹(shù)形結(jié)構(gòu):


[
    {
        "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實(shí)現(xiàn)list轉(zhuǎn)為樹(shù)形結(jié)構(gòu):

function listToTree(srcList) {
  let result = []
  // reduce收集所有節(jié)點(diǎn)信息存放在對(duì)象中,可以用forEach改寫(xiě),不過(guò)代碼會(huì)多幾行
  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
}

以上即為本文圍繞數(shù)組遍歷介紹的數(shù)組基本操作。這些高階函數(shù)其實(shí)都可以用于數(shù)組遍歷(如果想強(qiáng)行遍歷的話,比如some的callback恒返回false),不過(guò)實(shí)際使用中應(yīng)該根據(jù)不同的需求選用不同的方法。

至此,面試中遇到“數(shù)組遍歷有多少種方法?”這種問(wèn)題,你可以回答“10種以上”了,畢竟,本文介紹了12種...


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容