一文带你彻底搞懂js的Array.prototype.reduce()方法!





定义和用法

reduce是数组内置的一个方法,原型链上位于Array.prototype.reduce()

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

注意: reduce() 对于空数组是不会执行回调函数的。

reduce对数组累积执行回调函数,返回最终计算结果




语法

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • function(total, currentValue, currentIndex, arr)
参数描述
total必需。初始值, 或者计算结束后的返回值。
currentValue必需。当前元素
currentIndex可选。当前元素的索引
arr可选。当前元素所属的数组对象。
  • initialValue

可选。传递给函数的初始值

示例

1、求数组中所有元素的值的和

const arr01 = [10,20,30]
const result01 = arr01.reduce((total, currentValue)=>{
    return total + currentValue
}, 0)

console.log(result01)

2、求对象数组中指定属性值的和

const arr02 = [
    {x:10}, {x:20}, {x:30}
]

const result02 = arr02.reduce((total, currentValue)=>{
    return total + currentValue.x
}, 0)

console.log(result02)


3、将二维数组中的每个子数组元素进行合并,即二维数组扁平化

const arr03 = [
    [10, 20, 30, 40],
    [10, 20, 30, 40],
    [10, 20, 30, 40],
    [10, 20, 30, 40]
]

const result03 = arr03.reduce((total, currentValue) => {
    return total.concat(currentValue)
}, [])

console.log(result03)

4、统计数组元素的频数

将数组中每个元素(重复)出现的次数进行统计,最后将结果以对象的形式输出。

const arr04 = [
    "a", "b", "a", "b", "a", "b", "a", "b", "a", "a", "a", "a", "a", "b", "b", "b",
    "c", "c", "c", "c", "d", "d", "d", "e", "e", "e",
]

const result04 = arr04.reduce(
    (counterObj, currentValue) => {
        if(currentValue in counterObj){
            counterObj[currentValue] ++;
        }else{
            counterObj[currentValue] = 1;
        }
        return counterObj;
    }, 
    {}
)

console.log(result04)

5、分组

根据指定属性,对对象数组中的对象进行分组。

const arr05 = [
    { 年级: "2017级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2017级", 学院: "计算机学院", 专业: "计算机科学" },
    { 年级: "2018级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2019级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2020级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2020级", 学院: "计算机学院", 专业: "计算机科学" },
    { 年级: "2017级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2018级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2019级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2020级", 学院: "电气学院", 专业: "自动化" },
]


// 根据指定字段的指定值获取分组  例如: 按照年级分组 
// target 是"年级"  targetValue是"2017级别"
function getByTarget(arr, target, targetValue) {
    // 例如:如果不指定年级,则返回全部分组
    if (!targetValue) {
        const result = arr.reduce(
            (accumulator, currentObj) => {
            
                // 例如  currentTargetValue = "2017级"   target = "年级" 
                const currentTargetValue = currentObj[target]

                // 例如:当前的年级值是否在accumulator中
                if (!accumulator[currentTargetValue]) {
                    accumulator[currentTargetValue] = []
                }
                
                
                accumulator[currentTargetValue].push(currentObj)
                return accumulator;
            },
            {}
        )
        return result;
    }

    // 例如:如果指定了年级
    let initObj = {}
    initObj[targetValue] = [];
    const result = arr.reduce(
        (accumulator, currentObj) => {

            // 不是目标值直接跳过
            if (currentObj[target] !== targetValue) {
                return accumulator;
            }

            // 符合要求则加入
            accumulator[targetValue].push(currentObj);

            return accumulator;
        },
        initObj
    )
    return result;
}


result05 = getByTarget(arr05, "专业")

// 这里是为了确保函数返回值是一个map而不是数组(统一返回格式) 重在思路
result05_01 = getByTarget(arr05, "年级", "2017级")["2017级"]
result05_02 = getByTarget(result05_01, "学院", "计算机学院")["计算机学院"]

// 例如:我想获得2017级计算机学院中有什么专业
console.log(result05)
console.log(result05_01)
console.log(result05_02)

6、结合"…"使用

const result06 = arr06.reduce(
    (accumulator, currentObj) => {
        return [...accumulator, ...currentObj["tip"]]
    },
    []
)

console.log(result06)




7、数组元素的去重

const arr07 = [
    "a", "b", "a", "b", "a", "b", "a", "b", "a", "a", "a", "a", "a", "b", "b", "b",
    "c", "c", "c", "c", "d", "d", "d", "e", "e", "e",
]

const result07 = arr07.reduce(
    (accumulator, currentValue)=>{
        // 不存在则放入
        if(accumulator.indexOf(currentValue) === -1){
            accumulator.push(currentValue);
        }
        return accumulator;
    },
    []
)

console.log(result07)

8、遍历数组,同时对符合条件的数组元素进行操作。

const arr08 = [
    1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
     2, 2, 2, 2, 5, 6, 9, 8, 7, 4, 1, 2, 6, 3, 9, 5, 7
]

const result08 = arr08.reduce(
    (accumulator, currentValue) => {

        if (currentValue >= 5) {
            currentValue *= 10;
            accumulator.push(currentValue);
        }
        return accumulator;
    },
    []
)

console.log(result08)
console.log(arr08)



9、实现promise队列

Promise.all().then()的用法,all()表示所有的操作均执行完成,再执行then()中的方法。而all()中的函数,不是顺序执行,而是同时执行。如果希望then()之前的函数都是顺序执行的,可以考虑通过reduce函数来实现。

这就实现了一个promise队列

console.log("----------------------------------------")
const arr09 = [
    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(10);
                resolve(10);
            }, 1000)
        }
    ).then(res => { console.log(res) }),
    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(20);
                resolve(20);
            }, 1000)
        }
    ).then(res => { console.log(res) }),

    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(30);
                resolve(30);
            }, 1000)
        }
    ).then(res => { console.log(res) }),

    () => (40)
]

const result09 = arr09.reduce(
    (prev, next) => prev.then(next),
    
    // 这里是为了new一个promise形成链式调用
    Promise.resolve()
)
console.log(result09)

const createPromise = (id) => () => 
	new Promise(resolve=>
		setTimeout(()=>{
			console.log("promise->"+id+":"+new Date());
			resolve();
		},1000)
	)
var tasks = [createPromise(1),createPromise(2),createPromise(3)];
console.log(tasks);
var doTask = tasks.reduce((prev,next)=>prev.then(()=>next()),Promise.resolve());
doTask.then(()=>console.log("all done."));

其他写法

[1, 2, 3, 4, 5].reduce((pre, cur) => {
    return pre.then(() => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(cur)
                resolve()
            }, 1000)
        })
    })
}, Promise.resolve())

10、实现函数管道

const addOne = x => x + 1;
const addTwo = x => x + 2;
const pipe = (input) => (...functions) => functions.reduce(
        (accumulator, currentFunc) => {
            input = currentFunc(accumulator);
            return input;
        },
        input
    )

let initVal = 10;
let finalVal = pipe(val)(addOne, addTwo)
console.log(initVal);
console.log(finalVal)

仔细一看,有函数柯里化那味了。

const pipe = (...args) => (input) => {
    console.log(input); 
    console.log(args);
};
let result = pipe(1,2,3,4,5,6,7)(1)
console.log(result)

11、用reduce实现map函数

map相关概念

遍历数组的每一项,并执行回调函数的操作,返回一个对每一项进行操作后的新数组,

arr = []
arr.map(function(item,idx,arr){
    item //数组的每一项
    idx // 当前项的索引
    arr // 当前遍历的数组
    this // 函数体内的 this 为 callbackThis参数,
         // 如果不指定或者指定为 null和 undefined,则 this指向全局对象
},callbackThis)

实现

Array.prototype.myMap = function(func, callbackThis){
    // 最终返回新的数组
    let res = [];

    // 定义回调函数的执行环境
    // call 第2个参数传入null 则this指向全部对象
    let cbThis = callbackThis || null;

    this.reduce(
        (prev, next, index, arr)=>{
            // 传入map回调函数拥有的参数
            // 把每一项的执行结果push进入res
            res.push(func.call(cbThis, next, index, arr));
            
        }, 
        null);
    
        return res;
}
let arr11 = [1,2,3,4,5,6,7,8]
let result11 = arr11.myMap(Math.sqrt)

console.log(result11)



参考资料

reduce使用场景

MDN手册

Promise结合reduce构建顺序执行队列

菜鸟教程




完整示例代码

const arr01 = [10, 20, 30]
const result01 = arr01.reduce((total, currentValue) => {
    return total + currentValue
}, 0)

console.log(result01)

// ---------------------------------------------------

const arr02 = [
    { x: 10 }, { x: 20 }, { x: 30 }
]

const result02 = arr02.reduce((total, currentValue) => {
    return total + currentValue.x
}, 0)

console.log(result02)

// ---------------------------------------------------
const arr03 = [
    [10, 20, 30, 40],
    [10, 20, 30, 40],
    [10, 20, 30, 40],
    [10, 20, 30, 40]
]

const result03 = arr03.reduce((total, currentValue) => {
    return total.concat(currentValue)
}, [])

console.log(result03)

// --------------------------------------------------

const arr04 = [
    "a", "b", "a", "b", "a", "b", "a", "b", "a", "a", "a", "a", "a", "b", "b", "b",
    "c", "c", "c", "c", "d", "d", "d", "e", "e", "e",
]

const result04 = arr04.reduce(
    (counterObj, currentValue) => {
        if (currentValue in counterObj) {
            counterObj[currentValue]++;
        } else {
            counterObj[currentValue] = 1;
        }
        return counterObj;
    },
    {}
)

console.log(result04)

// -------------------------------------------------------

const arr05 = [
    { 年级: "2017级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2017级", 学院: "计算机学院", 专业: "计算机科学" },
    { 年级: "2018级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2019级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2020级", 学院: "计算机学院", 专业: "物联网" },
    { 年级: "2020级", 学院: "计算机学院", 专业: "计算机科学" },
    { 年级: "2017级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2018级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2019级", 学院: "电气学院", 专业: "自动化" },
    { 年级: "2020级", 学院: "电气学院", 专业: "自动化" },
]


// 根据指定字段的指定值获取分组  例如: 按照年级分组 
// target 是"年级"  targetValue是"2017级别"
function getByTarget(arr, target, targetValue) {
    // 例如:如果不指定年级,则返回全部分组
    if (!targetValue) {
        const result = arr.reduce(
            (accumulator, currentObj) => {

                // 例如  currentTargetValue = "2017级"   target = "年级" 
                const currentTargetValue = currentObj[target]

                // 例如:当前的年级值是否在accumulator中
                if (!accumulator[currentTargetValue]) {
                    accumulator[currentTargetValue] = []
                }


                accumulator[currentTargetValue].push(currentObj)
                return accumulator;
            },
            {}
        )
        return result;
    }

    // 例如:如果指定了年级
    let initObj = {}
    initObj[targetValue] = [];
    const result = arr.reduce(
        (accumulator, currentObj) => {

            // 不是目标值直接跳过
            if (currentObj[target] !== targetValue) {
                return accumulator;
            }

            // 符合要求则加入
            accumulator[targetValue].push(currentObj);

            return accumulator;
        },
        initObj
    )
    return result;
}


result05 = getByTarget(arr05, "专业")

// 这里是为了确保函数返回值是一个map而不是数组(统一返回格式) 重在思路
result05_01 = getByTarget(arr05, "年级", "2017级")["2017级"]
result05_02 = getByTarget(result05_01, "学院", "计算机学院")["计算机学院"]

// 例如:我想获得2017级计算机学院中有什么专业
console.log(result05)
console.log(result05_01)
console.log(result05_02)

// -------------------------------------------------------

const arr06 = [
    {
        name: "user01",
        tip: ["流行", "日语", "动漫"]
    },
    {
        name: "user02",
        tip: ["怀旧", "粤语", "经典"]
    },
    {
        name: "user03",
        tip: ["现代", "韩语", "电视剧"]
    }

]

const result06 = arr06.reduce(
    (accumulator, currentObj) => {
        return [...accumulator, ...currentObj["tip"]]
    },
    []
)

console.log(result06)


// -------------------------------------------------------

const arr07 = [
    "a", "b", "a", "b", "a", "b", "a", "b", "a", "a", "a", "a", "a", "b", "b", "b",
    "c", "c", "c", "c", "d", "d", "d", "e", "e", "e",
]

const result07 = arr07.reduce(
    (accumulator, currentValue) => {
        // 不存在则放入
        if (accumulator.indexOf(currentValue) === -1) {
            accumulator.push(currentValue);
        }
        return accumulator;
    },
    []
)

console.log(result07)


// -----------------------------------------------------

const arr08 = [
    1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 5, 6, 9, 8, 7, 4, 1, 2, 6, 3, 9, 5, 7
]

const result08 = arr08.reduce(
    (accumulator, currentValue) => {

        if (currentValue >= 5) {
            currentValue *= 10;
            accumulator.push(currentValue);
        }
        return accumulator;
    },
    []
)

console.log(result08)
console.log(arr08)


// ------------------------------------------------
console.log("----------------------------------------")

const arr09 = [
    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(10);
                resolve(10);
            }, 1000)
        }
    ).then(res => { console.log(res) }),
    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(20);
                resolve(20);
            }, 1000)
        }
    ).then(res => { console.log(res) }),

    () => new Promise(
        (resolve) => {
            setTimeout(() => {
                console.log(30);
                resolve(30);
            }, 1000)
        }
    ).then(res => { console.log(res) }),

    () => (40)
]

const result09 = arr09.reduce(
    (prev, next) => prev.then(next),
    Promise.resolve()
)
console.log(result09)

// ------------------------------------------------

console.log("----------------------------------------")

const addOne = x => x + 1;
const addTwo = x => x + 2;
const pipe = (input) => (...functions) => functions.reduce(
        (accumulator, currentFunc) => {
            input = currentFunc(accumulator);
            return input;
        },
        input
    )

let initVal = 10;
let finalVal = pipe(val)(addOne, addTwo)
console.log(initVal);
console.log(finalVal)




const arr10 = [
    let
]

const result10 = arr09.reduce(
    (prev, next) => prev.then(next.apply()),
    Promise.resolve()
)
console.log(result09)



Array.prototype.myMap = function(func, callbackThis){
    // 最终返回新的数组
    let res = [];

    // 定义回调函数的执行环境
    // call 第2个参数传入null 则this指向全部对象
    let cbThis = callbackThis || null;

    this.reduce(
        (prev, next, index, arr)=>{
            // 传入map回调函数拥有的参数
            // 把每一项的执行结果push进入res
            res.push(func.call(cbThis, next, index, arr));
            
        }, 
        null);
    
        return res;
}
let arr11 = [1,2,3,4,5,6,7,8]
let result11 = arr11.myMap(Math.sqrt)

console.log(result11)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值