JS数组reduce方法初识

2 阅读5分钟

JS数组reduce方法初识

前言:

从ES5到ES6之后有加了许多数组方法,本文来彻底了解一下reduce方法以及它的执行原理

reduce

具体了解一下reduce这个方法是用来干什么的,官方给出的解释也是用来求和的高阶函数

看一下它的语法

reduce(callbackFn) //没有初始值
reduce(callbackFn, initialValue) //设定初始值

来说明一reduce这几个参数的作用

const initialValue = 0;
arr.reduce((acc, cur, curIndex, array)=>{

},initialValue)

acc : 通常被称为累加器, 上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值。

cur : 当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]

curIndex : currentValue 在数组中的索引位置。在第一次调用时,如果指定了 initialValue 则为 0,否则为 1

array : 调用了 reduce() 的数组本身,这句话是官方给出的,稍有一些绕,说白了就是调用reduce方法的该数组

//数组求和
const sum = arr.reduce((acc, cur, curIndex, array) => {
  console.log(acc, cur, curIndex, array, 'acc, cur, curIndex, array');
  return acc + cur
},)
//计算数组中每个元素出现的次数
const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let countedNames = names.reduce(function (allNames, name) {
  console.log(allNames, 'allNames');

  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
console.log(countedNames, 'countedNames');

异常:

若未提供initialValue则会抛出错误异常,如下图错误

image.png

reduce有无初始值运行机制:

无初始值的 reduce() 代码:

const array = [15, 16, 17, 18, 19];

function reducer(accumulator, currentValue, index) {
  const returns = accumulator + currentValue;
  console.log(
    `accumulator: ${accumulator}, currentValue: ${currentValue}, index: ${index}, returns: ${returns}`,
  );
  return returns;
}

array.reduce(reducer);

回调函数会被调用四次,每次调用的参数和返回值如下表:

accumulatorcurrentValueindex返回值
第一次调用1516131
第二次调用3117248
第三次调用4818366
第四次调用6619485

array 参数在整个过程中始终不会改变——它始终是 [15, 16, 17, 18, 19]reduce() 返回的值将是最后一次回调返回值(85)。

有初始值的 reduce() 代码:

[15, 16, 17, 18, 19].reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  10,
);

回调函数会被调用五次,每次调用的参数和返回值如下表:

accumulatorcurrentValueindex返回值
第一次调用1015025
第二次调用2516141
第三次调用4117258
第四次调用5818376
第五次调用7619495

这种情况下 reduce() 返回的值是 95

reduce是js的高阶函数,它的功能很强大,大家可以根据以下例子来自行深究在项目中的高级用法

求对象数组中值的总和

为了对包含在对象数组中的值进行求和,必须提供一个 initialValue,以便每个项都通过回调函数处理

const objects = [{ x: 1 }, { x: 2 }, { x: 3 }];
const sum = objects.reduce(
  (accumulator, currentValue) => accumulator + currentValue.x,
  0,
);

console.log(sum); // 6

展平嵌套数组

const flattened = [
  [0, 1],
  [2, 3],
  [4, 5],
].reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
// flattened 的值是 [0, 1, 2, 3, 4, 5]

统计对象中值的出现次数

const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];

const countedNames = names.reduce((allNames, name) => {
  const currCount = allNames[name] ?? 0;
  return {
    ...allNames,
    [name]: currCount + 1,
  };
}, {});
// countedNames 的值是:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

按属性对对象进行分组

const people = [
  { name: "Alice", age: 21 },
  { name: "Max", age: 20 },
  { name: "Jane", age: 20 },
];

function groupBy(objectArray, property) {
  return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    const curGroup = acc[key] ?? [];

    return { ...acc, [key]: [...curGroup, obj] };
  }, {});
}

const groupedPeople = groupBy(people, "age");
console.log(groupedPeople);
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

使用展开语法和 initialValue 连接包含在对象数组中的数组

// friends——一个对象数组,其中对象字段“books”是最喜欢的书的列表
const friends = [
  {
    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,
  },
];

// allbooks——列表,其中包含所有朋友的书籍和 initialValue 中包含的附加列表
const allbooks = friends.reduce(
  (accumulator, currentValue) => [...accumulator, ...currentValue.books],
  ["Alphabet"],
);
console.log(allbooks);
// [
//   'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
//   'Romeo and Juliet', 'The Lord of the Rings',
//   'The Shining'
// ]

数组去重

const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const myArrayWithNoDuplicates = myArray.reduce((accumulator, currentValue) => {
  if (!accumulator.includes(currentValue)) {
    return [...accumulator, currentValue];
  }
  return accumulator;
}, []);

console.log(myArrayWithNoDuplicates);

使用reduce替换filter和map

个人认为这个用法会比较多一点,大家平时也可以自己多尝试

使用 filter()和 map()会遍历数组两次,但是你可以使用 reduce() 只遍历一次并实现相同的效果,从而更高效。(如果你喜欢使用 for 循环,你可以在遍历一次时使用 forEach()进行过滤和映射。)

const numbers = [-5, 6, 2, 0];

const doubledPositiveNumbers = numbers.reduce((accumulator, currentValue) => {
  if (currentValue > 0) {
    const doubled = currentValue * 2;
    return [...accumulator, doubled];
  }
  return accumulator;
}, []);

console.log(doubledPositiveNumbers); // [12, 4]

按顺序运行Promise

/**
 * 链接一系列 Promise 处理程序。
 *
 * @param {array} arr——一个 Promise 处理程序列表,每个处理程序接收前一个处理程序解决的结果并返回另一个 Promise。
 * @param {*} input——开始调用 Promise 链的初始值
 * @return {Object}——由一系列 Promise 链接而成的 Promise
 */
function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input),
  );
}

// Promise 函数 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// Promise 函数 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// 函数 3——将由 `.then()` 包装在已解决的 Promise 中
function f3(a) {
  return a * 3;
}

// Promise 函数 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(console.log); // 1200

使用函数组合实现管道

// 组合使用的构建块
const double = (x) => 2 * x;
const triple = (x) => 3 * x;
const quadruple = (x) => 4 * x;

// 函数组合,实现管道功能
const pipe =
  (...functions) =>
  (initialValue) =>
    functions.reduce((acc, fn) => fn(acc), initialValue);

// 组合的函数,实现特定值的乘法
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// 用例
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240

稀疏数组中使用reduce

reduce() 会跳过稀疏数组中缺失的元素,但不会跳过 undefined 值。

console.log([1, 2, , 4].reduce((a, b) => a + b)); // 7

console.log([1, 2, undefined, 4].reduce((a, b) => a + b)); // NaN

在非数组对象上调用reduce

reduce() 方法读取 this 的 length 属性,然后访问每个整数索引。

const arrayLike = {
  length: 3,
  0: 2,
  1: 3,
  2: 4,
};

console.log(Array.prototype.reduce.call(arrayLike, (x, y) => x + y));
// 9

结语:以上是我结合和MDN官方文档的demo做出的总结,也是作为自己对reduce的一个深度理解,后续也会持续更新其他知识点