面试中,你了解JS中的6种常用数组遍历方法吗?

发表时间: 2023-09-27 09:38

前端开发当中,我们通常要对后端返回的数据进行一些处理再渲染到页面,而其中常用的就是数组的不同遍历方法,因此熟练掌握这些方法是非常有必要的,而对于初学者来说,这些方法不太容易理解也容易被混淆,今天我们就通过本篇教会大家区别数组的forEach,map,filter,reduce,some,every这6种遍历方法。

一、这些方法的共同语法

除了reduce方法语法略有不同(后面单独讲解),其他五个方法forEach,map,filter,some,every传入的第一个参数语法相同

(1)第一个参数为回调函数:callbackFn(item,index,arr),该函数接收三个参数item,index,arr。

(2)三个参数分别表示:

item:当下遍历的数组元素的值;当数组的元素为基本数据类时,item是直接赋值为元素的值;当数组的元素为引用数据类型时,此时item是引用赋值,即该地址值会指向原数组的元素(在map方法里会举例说明)。

index:当下遍历的数组元素的索引;

arr:表示原数组。

下面我们通过具体讲解这些方法,来说明这些方法的不同之处以及使用场景。

二、forEach和map方法

forEach方法和map方法比较相似,所以我们这里一同讲解。首先我们了解一下这2种方法的基本概念:

(1)forEach方法:没有返回结果,返回值为undefined,本质上等同于 for 循环;

(2)map方法:会返回一个新数组,新数组的元素为原始数组元素调用函数处理的后return返回的值。

在大部分使用场景中,这2种方法都可以获得相同的结果,只是具体操作步骤有所不同,下面我们就以数组的数据类型为基本数据类型和引用数据类型2种情况举例。

2.1 数组数据类型:基本数据类型

假设我们有个数组[1,2,3,4,5],现在我们需要让数组的每个元素乘以2。

(1)使用forEach方法:

let arr = [1,2,3,4,5]

arr.forEach(function(item,index,arr){

arr[index] = item*2

})

console.log(arr) // [2,4,6,8,10]

// 用forEach方法改动原数组的元素,我们让原数组的每个元素变成了之前的2倍

这里我们使用forEach方法直接修改原数组,让原数组的每个元素直接替换为item*2,原数组就改成了我们需要的结果。

(2)使用map方法:

let arr = [1,2,3,4,5]

let newArr = arr.map(function(item,index,arr){

return item*2

})

console.log(newArr) // [2,4,6,8,10]

这里我们用map方法return出的item*2就是最终新数组的每个元素值,此时map方法不会改动原数组。如果不能改动原数组,此时就用map方法。

2.2 数组数据类型:引用据类型

假设我们有个对象数组,现在需要改动每个对象元素的属性。

(1)使用forEach方法:

let arr = [

{ id: '01001', title: '考研成绩' },

{ id: '01002', title: '中国经济复苏进度条' },

]

arr.forEach(function(item,index,arr){

item.date = "2023-1-1"

})

console.log(arr)

打印结果为:

这里我们使用forEach方法直接修改原数组,为原数组的每个元素都添加了date属性。

(2)使用map方法:

let arr = [

{ id: '01001', title: '考研成绩' },

{ id: '01002', title: '中国经济复苏进度条' },

]

let newArr = arr.map(function(item,index,arr){

item.date = "2023-1-1"

return item

})

console.log("arr",arr)

console.log("newArr",newArr)

打印结果为:

开头我们介绍这些方法的语法时有讲到,item如果是对象是引用数据类型就是引用赋值,所以直接改动item属性也会改动原数组。此时用map返回新数组的意义就不大,直接用forEach就可以实现这种效果。

而当我们需要不改动原数组时,我们先要对数据进行拷贝处理。举例如下:

let arr = [

{ id: '01001', title: '考研成绩' },

{ id: '01002', title: '中国经济复苏进度条' },

]

let newArr = arr.map(function(item,index,arr){

item = {...item} // 这里我们多了一步拷贝处理

item.date = "2023-1-1"

return item

})

console.log("arr",arr)

console.log("newArr",newArr)

打印结果如下:

此时我们可以看到原数组并没有被改动,新数组的是我们想要的结果。

另外,map方法因为会返回新数组,所以可以与reduce、filter等方法组合使用,以便在一条语句中执行多个操作,如下例所示:

let arr = [

{ id: '01001', title: '考研成绩', isHot: true },

{ id: '01002', title: '中国经济复苏进度条', isHot: false },

]

// 用map方法给数据加上日期属性

let result = arr.map((item) => {

item = {...item}

item.date = '2023-01-01'

return item

// map方法后紧接着使用filter方法过滤数据

}).filter((item) => {

return item.isHot === true

})

console.log(result)

上面代码的打印结果如下,此时数据既添加了date属性,又过滤出了isHot为true的数据。

三、filter方法

3.1 概念

定义:遍历数组并返回一个新的数组,新数组中的元素是通过检查指定数组中满足条件的所有元素。(即过滤数组并返回新数组,不会改变原数组)

3.2 举例

注意:传入的函数里必填return,因为会根据return的值为false或true来过滤数据。

(1)直接return布尔值,为true则元素值放入数组中,为false的就被过滤掉。

let arr = [

{ id: '01001', title: '考研成绩', isHot: true },

{ id: '01002', title: '中国经济复苏进度条', isHot: false },

]

let result = arr.filter((item) => {

return item.isHot

})

console.log(result) // [{ id: '01001', title: '考研成绩', isHot: true }]

// 看打印结果可以发现isHot为false的对象数据就被过滤掉了

(2)return非布尔值时,也会通过将数据隐式转化为布尔值来过滤数组。

let arr = [1,undefined,null,3,0,"",NaN]

let result = arr.filter((item) => {

return item

})

console.log(result) // [1,3]

// 将item的值转化为布尔值后,为false的元素就被过滤掉了,留下的为true的

(3)与其他方法结合使用:

这里先用一个小例子帮大家回忆一下数组的indexOf()方法的用法:用于返回某个指定的值在数组中首次出现的索引值,如果没有找到匹配的元素则返回 -1。

let arr = [1, 2, 3, 4, 5];

let index = arr.indexOf(2);

// 在数组中查找是否有2这个元素

console.log(index) //打印结果是1,表示2和数组中索引为1的元素的值匹配

①与indexOf()方法组合使用进行数组去重:

let arr = [1,1,2,4,5,6,5,5,6]

let newArr = arr.filter((item,index)=>{

return arr.indexOf(item) === index

// 因为indexOf始终返回第一个符合条件的元素的索引

// 数组中重复出现的数值就不可能满足全等判断,就会被过滤掉

})

console.log(newArr) // [1,2,4,5,6]

②还有与map方法一起使用的例子,map方法介绍中已举例,这里就不重复说明了。

四、reduce方法

4.1 概念

(1)定义:遍历数组,并构建返回一个最终的值。

(2)语法:

array.reduce(function(previous,current,index,arr),initValue);

(3)参数说明:

①不传第二参数initValue时,我们以一个计算数组元素相加之和的例子说明:

let arr = [1,3,5,7]

let result = arr.reduce((previous,current)=>{

console.log('previous:',previous, ' current:',current)

return previous + current

})

console.log('result:',result)

打印结果为:

我们可以看到:

(a)传入的箭头函数执行了数组长度-1次,也就是3次;

(b)回调函数多次调用:

第一次调用时,previous表示就是数组的第一个元素的值1,current是数组第二个元素的值3;

第二次调用时,previous表示的是上次调用时return出来的值也就是1+3为4,current是数组第三个元素的值5;

第三次调用时,previous同样表示的是上次调用返回的值也就是4+5为9,current表示数组第四个元素的值7。

(c)result的值就是最后一次计算9+7的结果值16。

②传入第二参数initValue时,我们以一个获得新数组的每个元素是原数组每个元素累计相加之和的例子说明:

let arr = [1,3,5,7]

let sum = 0

let result = arr.reduce((previous,current)=>{

console.log(previous, 'current:', current)

previous.push(sum + current)

sum += current

return previous

}, [])

console.log('result:', result)

打印结果为:

我们可以看到:

(a)传入的箭头函数执行了arr数组长度一样的次数,也就是4次;

(b)previous:箭头函数第一次调用时,表示的是传入的初始值initValue,也就是空数组,后面表示的是上次return出来的值;current:始终表示的是数组arr的每个元素的值;

(c)result同样表示最后一次箭头函数调用return返回的结果。

4.2 举例

上面我们举了2个简单例子区分reduce方法传入1个参数和2个参数的区别,下面我以一个计算购物车选中产品数量的例子来说明项目中reduce的具体应用场景。

目前购物车一共有3个产品,其中选中了2种产品,并计算选中的总价为22393,我们可以通过reduce方法计算①②这个2个值。

// arr表示购物车里所有产品数组,其中用不到的数据就省略了

let arr = [

{

id:12334,

isChecked:1, // 1表示购物车选中了这个产品

cartPrice:5999, // 表示产品单价5999

skuNum:1, // 表示购物车产品数量为1

skuName:"小米10 至尊纪念版 双模5G 骁龙865 120W快充 8GB+128GB 陶瓷黑 游戏手机",

},

{

id:12375,

isChecked:0, // 0表示购物车没有选中这个产品

cartPrice:2323, // 表示产品单价为2323

skuNum:1, // 表示购物车产品数量为1

skuName:"华为P40 5G全网通智能手机 支持鸿蒙HarmonyOS 零度白 8G+128G",

},

{

id:12376,

isChecked:1, // 1表示购物车选中了这个产品

cartPrice:8197, // 表示产品单价为8197

skuNum:1, // 表示购物车产品数量为1

skuName:"Apple iPhone 12 (A2404) 64GB 黑色 支持移动联通电信5G 双卡双待手机",

},

]

因为每个产品对象的isChecked属性1就表示选中了,0表示没有选中,因此我们可以通过累计相加这个值来计算购物车选择的产品数:

// 计算购物车选中产品数

let num = arr.reduce((previous,current)=>{

return previous + current.isChecked

// previous初始值是传入的第二个参数0,current表示数组的当前遍历到的对象

},0)

// 通过累计相加所有产品的isChecked的属性值,获得选中的产品数量num为2

计算选中产品总价格,我们也是通过reduce方法累计计算:

// 计算购物车选中产品总价格

let price = arr.reduce((previous,current)=>{

return previous + current.isChecked * current.cartPrice * current.skuNum

// 选中的产品的数量和价格的乘积累计相加

},0)

// 最终的price的值就是选中产品总价

通过上面的例子我们可以看到这种要累计计算处理数据的情况,使用reduce方法是比较方便的操作。

五、some和every方法

因为some()方法和every()方法比较简单,因为比较相似所以这里也是一起讲解。

概念及举例

(1)some()方法用于检测数组中的元素是否满足return的判断条件,如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测;如果没有满足条件的元素,则返回false。

// some方法只要有一个元素符合return的条件就返回true,后面元素就不会再执行判断

let arr = [2,3,4,6]

let result = arr.some((item)=>{

return item % 2 === 1 // 判断数组的每个元素是否除以2能余1

})

console.log(result) // 因为第二个元素3符合,所以结果为true

(2)every()方法用于检测数组中的元素是否都满足return的判断条件,只要有一个不符合就会返回false,都符合才返回true。

// every方法要求所有元素符合return的判断条件才返回true,不然就返回false

let arr = [2,3,4,6]

let result = arr.every((item)=>{

return item % 2 === 0 // 判断数组的每个元素是否都能被2整除

})

console.log(result) // 因为第二个元素3不符合条件,所以结果为false

总结

(1)forEach方法没有返回值,一般用于直接修改原数组;

(2)map方法会返回新的数组,在处理元素为引用数据类型的数组时可以通过数据的拷贝不修改原数组(拷贝的方法我们会在下回和大家做专门的讲解),并且可以结合其他方法执行更多层的操作;

(3)filter()方法用于过滤数组,返回的结果就是过滤后的新数组;

(4)reduce()方法在一般需要对数组元素的数据进行一些运算处理时使用,返回的值就是最终的结果;

(5)some()方法用于判断数组中是否有满足条件的元素,返回结果是布尔值,只要有一个符合就是true;

(6)every()方法用于判断数组中的元素是否都符合判断条件,返回结果是布尔值,都符合才会返回true。