JS Array.reduce 理解与使用

这个 reduce 函数,说起来其实也挺简单的,但是要实用起来有时候还是感觉有些困难
这是我自己的一些理解方式,分享给大家看一下,希望能帮助到不理解的小伙伴。直接用代码进行逐步的讲解
可能会比较啰(话)嗦(多),但是你看完了,应该就会用了

急性子可以直接去看 更多例子下的二维数组合并 看完应该也能理解
急性子可以直接去看 更多例子下的二维数组合并 看完应该也能理解

我们先来看看主角 reduce 都有哪些参数 【这段内容相对比较重要】
Array.reduce( Function, initValue )
两个参数都非常的重要(虽然第二个可有可无)
Function: 不用说,肯定是个回调函数。由开发者提供 reduce 来回调
initValue: 第一次回调 Function 时会将 initValue 传入到 Function 的第一个参数中

ps: 插入一段内容,我发现有的博主没有说。就是当 initValue 没有传入的时候,Function 的第一个参数,将会是数组 0 所对应的值届时 reduce 也将会少走一次循环


接下来说一下 Function 回调时 reduce 都给了哪些参数
function(pre, cur, index, arr)
pre: 上一次的结果集,这个值通常会使用 initValue 作为第一次的默认参数,不过,我上面也有提到,当 initValue 没有传入参数时这个值将会是 数组 的第一个值
cur: 当前元素,与我们使用 forEach/map 时的 item 一样。都是当前循环到的那个对象
通常我们只会使用上面的两个属性
index: 当前元素的索引
arr: 当前元素所属的数组

看完了上面对 reduce 之后,应该就会对 reduce 有一个模糊的了解了,接下来看看它与平常使用方式区别

//跟其他博主一样,我们也用要给简单的数组开始讲
//需求:我们现在有如下这么一个数组,我们想要获得这个数组的总和
let arr = [1,2,3,4,5];

方式一

传统方式,使用一个循环来循环累加,
下面代码是对传统方式的一种例子
这种方式完全可以实现,并且代码量其实也并不复杂。但是我们既然要学习新的高阶函数,那么我们就不在推荐大家使用这种方式了,接下来请看【二】
ps:大家既然都来学 reduce 了,我就默认大家都会使用 => 箭头函数了,这里不对箭头函数做专门的讲解了

let count = 0;
arr.forEach(item=>count+=item);
console.log(count); //15

这方式一实在是太简单了吧。。。我就不讲了

方式二

使用 reduce 函数
如下代码:

let count = arr.reduce(function(pre, cur){
    return pre+cur
});
console.log(count); //15

看到这里,你可能会有疑问,这代码量丝毫不比传统方式少,但是为什么还要用这种方式
如果你有这种疑问,那你可能就对 箭头函数 还不够理解,接下来我就要变形了!

上面都说了不讲 箭头函数 了,但是我这里还是想用用 function 因为直接用 箭头函数 会变得比较难以理解,上面的那个代码纯粹是为了给初学者带来更好理解的一种方式。希望大家把这两种方式都牢记,能够在心里进行转换

方式二变形

let count = arr.reduce((pre,cur)=>pre+cur)

二维数组合并

//需求:将如下二维数组进行合并处理
let arr2 = [[1,2,3], [4,5,6], [7,8,9]]

插播一则小广告:我这里有一个我自己封装的多维数组拍平也是使用的 reduce 感兴趣的小伙伴可以来瞅瞅 点我查看 看完下面的代码,相信你也能写出来。好了,继续

let newArray1 = arr2.reduce(function(pre,cur){
    return pre.concat(cur);
}, []);
let newArray2 = arr2.reduce((pre,cur)=>pre.concat(cur), []);
console.log(newArray2); //[1,2,3,4,5,6,7,8,9]

讲解一下代码执行原理
reduce 第一次进入循环时,会将我们的第二个参数传入到 pre 中
----此时 pre = [], cur = [1,2,3];
----然后我们将 pre 与 cur 使用 concat 拼接成了一个新的数组,并 return
reduce 第二次进入循环
----此时 pre = [1,2,3], cur = [4,5,6]
----然后我们再次将 pre 与 cur 使用 concat 拼接成了一个新的数组,并 return
reduce 第三次进入循环(由于 arr2 长度也就 3 所以这也是最后一次)
----此时 pre=[1,2,3,4,5,6], cur=[7,8,9]
----然后我们再次将 pre 与 cur 使用 concat 拼接成了一个新的数组,并 return
reduce 退出循环 并将 pre 抛出
我们将会得到最后的结果 [1,2,3,4,5,6,7,8,9]

附文:

我们上面有提到过,当我们的 initValue 不传入时,reduce 会将数组第一位作为初始值,并且我们事先知道我们需求中,二维数组是固定的两层数组 如:表格 那么我们这时就可以省掉 reduce 的第二个 initValue 参数(虽然节约不了太多的性能)

当我们去掉 initValue 参数时,我们得到的结果是这样的
reduce 第一次进入循环
----此时 pre = [1,2,3], cur = [4,5,6]
----然后我们将 pre 与 cur 使用 concat 拼接成了一个新的数组,并 return
reduce 第二次进入循环(我们这里其实循环就不再从 0 开始了,而是从 1 开始,所以这也是最后一次循环)
----此时 pre=[1,2,3,4,5,6], cur=[7,8,9]
----然后我们再次将 pre 与 cur 使用 concat 拼接成了一个新的数组,并 return
reduce 退出循环 并将 pre 抛出
我们将会得到最后的结果 [1,2,3,4,5,6,7,8,9]

五个例子

//需求1,对下面数组进行去重处理
//需求2,找到下面数组中的最大数和最小数
//需求3,得到下面数字每次出现的次数
let arr = [1,2,3,1,2,3,4,5,6];
//需求4,对如下数组,根据 name 进行去重处理
let arr2 = [
    {name: '小杜'},
    {name: '小杜'},
    {name: '李四'},
    {name: '王五'},
    {name: '王五'},
    {name: '小青'},
    {name: '小青'},
    {name: '桃子'},
    {name: '小刘'}
];
//需求5,对如下数组根据姓名首字母分类,得到 arr3_result 例子结果
let arr3 = [
    { name: 'Bob' },
    { name: 'Ben' },
    { name: 'Cole' }, 
    { name: 'Mary' }, 
    { name: 'Travis' }
]
let arr3_result = {
    B: ['Bob', 'Ben'],
    C: ['Cole'],
    M: ['Mary'],
    T: ['Travis']
}
//需求1,对数组进行去重处理
let arr_result = arr.reduce((pre, cur) => {
    !!!pre.includes(cur) && pre.push(cur);
    return pre;
}, []);
console.log(arr_result); //[1,2,3,4,5,6]

//需求2,找到数组中的最大数和最小数
console.log("最大数", arr.reduce((pre, cur) => pre > cur ? pre : cur))     //6
console.log("最小数", arr.reduce((pre, cur) => pre < cur ? pre : cur))     //1
//需求3, 得到各数字每次出现的次数
console.log("各出现次数结果", arr.reduce((pre, cur) => {
    let val = pre[cur] || 0
    pre[cur] = ++val
    return pre
}, {}))
//需求4,根据 name 进行去重处理
let arr2_result = arr2.reduce((pre, cur, index) => {
    if (!pre.names.includes(cur.name)) {
        pre.names.push(cur.name)
        pre.unique.push(cur)
    }
    if(index === arr2.length-1){
        return pre.unique   //最后一次循环时,将唯一的数组丢出
    }else{
        return pre
    }
}, {unique: [], names: []});    //传入两个对象,unique保存名字唯一的对象, names保存唯一的名字
console.log(arr2_result);
//[{name:'小猴'},{name:'小玉'},{name:'桃子'},{name:'小刘'}]
//需求5,对数组 arr3 进行根据姓名首字母分类,得到 arr3_result 例子结果
let tt = arr.reduce(function (pre, cur) {
    let name = cur.name     // 2.从当前的对象中取出 name 的值
    let letter = name[0]    // 3.从 name 中取出首字母

    let names = pre[letter] // 4.将 letter 从对象中取出
    if(names === undefined) names = []  // 4.1 pre 中是否已经存在 letter 的数组,如果没有,则新建一个数组

    names.push(name)        // 5.将姓名放到 letter 对应的数组中
    pre[letter] = names     // 6.重写 pre letter对应的数组

    return pre  // 7.将 pre 丢出作为下一次循环的默认值
    // 1.传递一个空对象给 reduce 作为默认值
}, {})
console.log(tt)

更多例子

下面我们举一些其他的例子来讲解

数组求和与数组求平均值

数组与树形相互转换

实现一个我们自己的 reduce

现在,通过我们对上述代码学习后,应该有一点初步的了解了,下面我们对 reduce 进行深入的研究,实现一个我们自定义的 reduce

// 例子
function Array2(arr) {
    this.arr = arr
    this.reduce = function (func, arg) {
        let index = arg === undefined ? 1 : 0   //获得循环起始位置,当没有传默认值时,从 1 开始循环
        let pre = arg || this.arr[index]        //当给了默认值,则首次使用默认值,否则使用数组 0 的值作为上一次的值
        for (let i = index; i < this.arr.length; i++) {
            //获得回调结果,将结果作为最后最后一次值
            pre = func(pre, this.arr[i], i, this.arr) // 回调的四个参数:上一次结果,当前值,当前索引,当前数组
        }
        return pre //将结果抛出
    }
}

// 使用
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1, 9, 3, 2, 5, 6, 23, 47, 5, 3, 4, 6, 7, 2, 0, -2, -20, -66]
console.log("去重结果", new Array2(arr)
    .reduce((pre, cur) => {
        !pre.includes(cur) && pre.push(cur)
        return pre
    }, [])
)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容