【深度总结】 JS数组 reduce 相关的方法,原理,应用,还有 polyfill 实现的 reduce, 其实你可以直接调到最后一章,了解一下 reduce 的实现即可。
💁♂️ Array.reduce
- 简述:用于迭代累加。
- 语法:
arr.reduce(callback [, initialValue]) - 参数:
- callback: 针对每一项执行的函数。
- inititalValue: 可选,累加执行的初始值。
- callback
- (acc) 初始值/上一次回调的返回值值
- (cur) 当前值
- (idx) 可选,当前索引
- (src) 可选,源数组
- 描述: 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素。
💁♂️ 基本用法:
const array = [1, 2, 3, 4];
const reducer = (pre, cur) => pre + cur;
// 10 + 1 + 2 + 3 + 4
console.log(array.reduce(reducer, 10));
💁♂️ Tips:
- (1) reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
- (2) 如果没有初始值,pre 将使用数组中的第一个元素。
- (3) 在没有初始值的空数组上调用 reduce 将报错。
- (4) 再没有初始值的仅有一个元素的数组上使用reduce,那么callback 不会 被执行 ,此唯一值将被返回。
💁♂️ 问题
下面代码中,Array.reduce(callback) 的callback函数被执行了几次
[8, 1, 2, 3, 4].reduce(function(pre, cur, index, array){
return pre + cur;
});
分析:
- 没有初始值的时候,callback 被执行了4次。 第1次执行的时候: pre=8,cur=1,index=1,array是数组。
- 在有初始值的时候, callback 会被执行 5 次。第1次执行的时候:pre=初始值,cur=8,index=0,array是数组。
- 初始值是0,也是有初始值,也会被执行 5 次。
💁♂️ 应用
📝 1. 数组每一项的和
var total = [ 0, 1, 2, 3 ].reduce( ( acc, cur ) => acc + cur, 0);
📝 2. 累加对象数组中的值
var init = 0;
var sum = [{x: 1}, {x:2}, {x:3}].reduce( (pre, cur) {
return pre + cur.x;
}, init)
- 注意: 这里必需使用初始值,以保证每一个回调函数都被执行。
📝 3. 二维数组转换成一维
var array = [[0, 1], [2, 3], [4, 5]];
var flattened = array.reduce( ( pre, cur ) => pre.concat(cur), [] );
- 注意: 这里的初始值应该为空数组,以保证第一次应用callback的时候
pre = [], pre才可以使用[].concat方法进行数组合并。
📝 4. 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var countedNames = names.reduce(function (allNames, name) {
if (name in allNames) {
allNames[name]++;
}
else {
allNames[name] = 1;
}
return allNames;
}, {});
- 注意:这里 allNames 使用了
pre 是所有已经处理过内容的集合这一特性。 - 使用: in 运算符判断属性名是否存在:
属性名 in 对象。 - init 设置为空对象。
📝 5. 按属性对object分类
var people = [
{ name: 'Alice', age: 21 },
{ name: 'Max', age: 20 },
{ name: 'Jane', age: 20 }
];
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// {
// 20: [
// { name: 'Max', age: 20 },
// { name: 'Jane', age: 20 }
// ],
// 21: [{ name: 'Alice', age: 21 }]
// }
📝 6. 合并对象数组中的某一项
var people = [{
name: 'Anna',
books: ['Bible', 'Harry Potter'],
age: 21
}, {
name: 'Bob',
books: ['War and peace', 'Romeo and Juliet'],
age: 26
}, {
name: 'Alice',
books: ['The Lord of the Rings', 'The Shining'],
age: 18
}];
// 合并所有书的名字
var allbooks = friends.reduce(function(prev, curr) {
return [...prev, ...curr.books];
}, []);
// allbooks = [
// 'Bible', 'Harry Potter', 'War and peace',
// 'Romeo and Juliet', 'The Lord of the Rings',
// 'The Shining'
// ]
📝 7. 数组去重
let arr = [1,2,1,2,3,5,4,5,3,4,4,4,4];
let result = arr.sort().reduce((pre, current) => {
// 原理看解释
if(pre.length === 0 || pre[pre.length-1] !== current) {
pre.push(current);
}
return pre;
}, []);
console.log(result); //[1,2,3,4,5]
- 数组是空的时候,直接添加这一项
- 数组的最后一项
(pre[pre.length-1])和当前值(cur)不相等的时候,也要添加,因为现在的数组是排好序的arr.sort(),pre的最后一项和现在这项不等: 说明这是一个新的数字,需要添加到数组中。
📝 8. 按顺序运行Promise
// arr: Promise数组
// input: 其它参数
function runPromiseInSequence(arr, input) {
return arr.reduce( function(promiseChain, currentFunction) {
return promiseChain.then(currentFunction)
}, Promise.resolve(input) )
}
// promise function 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5);
});
}
// promise function 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2);
});
}
const promiseArr = [p1, p2];
runPromiseInSequence(promiseArr, 10).then(console.log); // 100
💁♂️ polyfill : 自己实现一个 Array.reduce()
在实现一个简单的 Array.reduce()之前,先回顾一下 Array.reduce()的几个关键点。划重点~
- 1、 Array.reduce 是Array.prototype 上的原生方法。
- 2、 Array.reduce 接受两个参数: callback函数 和 init 初始值[可选]
- 3、 👨💻 没有初始值 && 数组是空 => 报错
- 4、 👨💻 没有初始值 && 数组长度1 => 直接返回这一项,不执行callback
- 5、 👨💻 没有初始值 && 长度大于1 => 这是一个正常的没有初始值数组 => 从第二项开始执行。prev = array[0] curr=array[1]
- 6、 👨💻 有初始值 && 长度大于1 => 从第一项开始执行 => prev=init curr = array[0]
- 7、 👨💻 reduce 函数的返回值是 prev。
// ① 定义到 Array.prototype 上。 函数有两个参数
Array.prototype.myReduse = function(callback, init) {
// init 是可选的, 这里的 this 是调用的那个array。
init = init || null;
const array = this;
// ② 定义 prev 之前的结果 和 循环起始点
let prev, startIndex = 0;
// ③ 没有初始值 && 数组是空 => 报错
if(!init && array.length == 0 ) return console.log('空数组,没有初始值');
// 没有初始值 && 数组长度=1 => 直接返回第一项,callback不执行
if(!init && array.length == 1 ) return array[0];
// 没有初始值 && 长度大于1 => 从第二项开始,prev = 第一项的值
if(!init && array.length > 1) {
prev = array[0];
startIndex = 1;
}
// 有初始值 && 长度大于1 => 从第一项开始,prev = init 初始值
if(init && array.length > 1) {
prev = init;
}
for(let index = startIndex; index < array.length; index ++) {
// prev 始终是回调函数执行的结果,
// callback 按照顺序传入值: 前面的结果, 当前项的值, 当前索引,原数组
prev = callback(prev, array[index], index, array);
}
// 返回之前项的结果。
return prev;
}