JS中的map、forEach用法解析

253 阅读6分钟

forEach:遍历循环数据的每一项,不改变原数组

写法:

array.forEach( (element, index, obj) => {
    // forEach是没有返回值,返回值为undefined,并且不可链式调
});
// element:数组当前项的值
// index:数组当前项的索引
// obj:数组对象本身

map: 返回一个由原数组中的每一项调用一个指定方法后的返回值组成的新数组,会分配内存空间储存新数组并返回

写法:

array.map( (element, index, obj) => {

    // element:数组当前项的值
    // index:数组当前项的索引
    // obj:数组对象本身
    
    return element
    // 必须要有返回值,如果不给return,它会返回一个undefined
    // 它不会影响原数组,只是将原来的数组拷贝了一份,把拷贝的数组项进行更改,支持链式调用
});

let arr = ['aaaa','bb','dd','ddd']
let arr1 = arr.map((element, index, obj) => {
    return element+'0'
})
console.log( arr )  // ['aaaa', 'bb', 'dd', 'ddd']
console.log( arr1 ) // ['aaaa0', 'bb0', 'dd0', 'ddd0']

filter:不会改变原有数组,返回的是过滤后的新数组, 在调用filter之后添加到数组中的元素不会被filter遍历到。

写法:

arr.filter((element, index, obj) => {
    // element:数组当前项的值
    // index:数组当前项的索引
    // obj:数组对象本身  
})
let arr = ['aaaa','bb','dd','ddd']
arr.push('ee')
let arr2 = arr.filter( element => {
    // 回调函数返回的结果一个boolean值,若结果为真,则返回匹配的项;否则,返回一个空数组
    return element.length <3
})  
// filter函数遍历的元素范围在第一次调用回调函数callback的时候就已经确定
arr.push('ff')

console.log( arr )  // ['aaaa', 'bb', 'dd', 'ddd','ee', 'ff']
console.log( arr2 ) // ['bb', 'dd','ee']

性能上: for循环>forEach>map

可读性: forEach/map>for循环

区别: for循环是按顺序遍历,按照下标索引的方式进行读取访问元素的,随机访问,而forEach/map等是使用iterator迭代器进行遍历,先取到数组中的每一项的地址放入到队列中,然后按顺序取出队里的地址来访问元素。

那关于map和forEach的用法选择问题,会有人说,forEach会改变原数组;map不会改变原数组,返回一个新数组。事实是这样的吗?答案不是,或者说不完全是,这种说法不准确,是有条件的。我曾经看过有人提出过这样一个生产中的现象,能很好的解释这个问题:

    const student = [
        {
            name:'student1', sex:'mela', password:'111'
        },
        {
            name:'student2', sex:'mela', password:'111'
        },
        {
            name:'student3', sex:'mela', password:'111'
        },
        {
            name:'student4', sex:'mela', password:'111'
        },
    ]
    student.forEach( item => delete item.password)
    student.map( item => delete item.password)
    console.log( student )

我当时听的时候有点似懂非懂但看这代码大概说得过去,也没什么疑问,在我做课后复习的时候,到这里这个map我就感觉不太对劲了——为什么map这种会生成一个新数组的函数,可以不声明一个量来接受它并且还运行的没问题.

我也请教了老师,老师说此处forEach更好一些,可我还是对这个map有疑问,因为map在没有任何数组来接受它的情况下仍然改变了原数组,实现的效果和forEach一样.

当数组的值为基本类型的时候,map遍历数组,当对数组中的值做处理的时候,的确不会改变原数组。 例如上面的map例子直接修改了每个 item 的属性,这是导致改变原数组的根本原因,实际开发时,不建议这么做,而是应该用forEach 或者 重新定一个变量来进行存储

如果您打算通过应用函数来更改数组元素,则应该使用map方法,因为它不会修改原始数组并返回一个新数组。这样,原始数组保持不变。另一方面,如果您想遍历数组的所有元素并且不需要返回的数组,请使用该forEach方法。

除此之外,功能非常相似。

数组的其他方法如下:

生成数组:

let array = Array.from({ length: 21 }, (_, i) => i)

array = [...Array(3).keys()]


// push
console.log(array.push("00")) // 4
console.log(array) // [ 0, 1, 2, '00' ]

// pop
console.log(array.pop()) // 00
console.log(array) // [ 0, 1, 2 ]

// shift
console.log(array.shift()) // 0
console.log(array) //[ 1, 2 ]

// unshift
console.log(array.unshift(0)) // 3
console.log(array) // [ 0, 1, 2 ]

//splice 更改原数组  返回删除的元素 从第1位删除2个值
console.log(array.splice(1, 2, 'a', 'b'))  //  [ 1, 2 ]
console.log(array) // [ 0, 'a', 'b' ]

// slice 不更改原数组 返回被截取的值  开始位置 结束位置(不包括结束位置)
console.log(array.slice(1, 2))  //  [ 'a' ]
console.log(array) // [ 0, 'a', 'b' ] 

// concat 不更改原数组 生成新的合并数组
console.log(array.concat([11, 22]))  //  [ 0, 'a', 'b', 11, 22 ]
console.log(array) // [ 0, 'a', 'b' ] 

// fill
const phonenumber = "15888889999"
console.log(phonenumber.split("").fill('*',3,7).join("")) // 158****9999

// 表面上看是用空对象填充了一个数组对象,实际上有一个巨大的坑
// 因为是使用同一个对象来填充数组
// 而对象是引用数据类型,所以造成数组中的所有对象都是联动的,牵一发而动全身
// 所以数组填充基础类型的数据是可以的,引用数组类型不可用
let data = new Array(5).fill({ id: "", name: "" })
console.log(data)
// [
//     { id: '', name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' }
// 
data[0].id = 0
console.log(data)
// [
//     { id: 0, name: '' },
//     { id: 0, name: '' },
//     { id: 0, name: '' },
//     { id: 0, name: '' },
//     { id: 0, name: '' }
// ]

// 可以使用Array.form
let data = Array.from({ length: 5 }, () => { return { id: "", name: "" } })
// [
//     { id: 0, name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' },
//     { id: '', name: '' }
// ]



// find 返回满足条件的第一个元素 没有则返回 undefined
// findIndex 返回满足条件的第一个元素的下标 没有则返回 -1
// indexOf 返回满足条件的第一个元素的下标 没有则返回 -1

const findarr = [1, 2, 3, 4, 5]
const findOBJECT = [{ id: 1 }, { id: 2 }, { id: 3 }]
findarr.find((item) => item > 2)
console.log(findarr.find((item) => item > 2)) // 3
console.log(findarr.find((item) => item > 7)) // undefined
console.log(findarr.findIndex((item) => item > 2)) // 2
console.log(findarr.findIndex((item) => item > 7)) // -1
console.log(findOBJECT.findIndex((item) => item.id > 1)) // 1

// find findIndex es6
// indexOf es5
// indexOf 方法将元素作为参数;而在 findIndex 方法中,回调函数作为参数传递。
// indexOf 方法非常适合获取简单元素的索引。但是,当我们寻找更复杂的事物(例如对象)的索引时,此方法无法正常工作。这是因为“引用相等”。根据引用相等,只有当被比较的两个对象引用完全相同的对象时,比较才会返回 true。在 JavaScript 中,两个相同的对象并不相同,除非它们引用同一个对象。
const obj = { id: "aaa" }
const arrobj = [{ id: "a" }, { id: "aa" }, obj]
console.log(arrobj.indexOf({ id: "aaa" })) // -1
console.log(arrobj.findIndex(item => item.id === "aaa")) // 2
console.log(arrobj.indexOf(obj)) // 2

// -------------------------------结论---------------------------------
// indexOf 和 findIndex 方法都返回指定元素的第一个索引。 indexOf 方法将要返回索引的元素作为参数,而 findIndex 方法将函数作为参数。但这两个函数都返回“-1”作为输出。


// flat 数组扁平化成一维数组(维度-1) 返回新数组 不影响原数组 可链式调用
const flatarr = [1, 2, [3, 4, ['a', ['aa']]]]
console.log(flatarr.flat()) // [ 1, 2, 3, 4, [ 'a', [ 'aa' ] ] ]
console.log(flatarr.flat().flat()) // [ 1, 2, 3, 4, 'a', [ 'aa' ] ]  
console.log(flatarr) // [ 1, 2, [ 3, 4, [ 'a', [Array] ] ] ]