山谷萤火虫

V1

2023/03/28阅读:44主题:橙心

从深入理解到手写数组reduce方法

JavaScript中经常遇到对数组进行循环处理的场景,最常用的方法是使用forforEachmap等,其实很多情况可以用数组方法reduce实现, 比如对数组进行累加或累乘。

这篇文章主要介绍下数组方法reduce的基本使用及手写一个简单的reduce方法。

方法简介

Array.prototype.reduce(callbackfn[,initialValue])

功能说明

reduce方法对数组中的每个元素按序执行一个外部提供的函数,每一次运行该函数会将之前的计算结果作为下一次执行函数的参数传入,最后将其结果汇总为单个返回值

参数说明

callbackfn: 回调函数,必选, 支持4个参数

  • previousValue: 上一次调用callbackfn得到的返回值。

第一次调用时,如果指定了初始值initialValue, 其值为initialValue,否则为数组的第一个元素array[0]

  • currentValue: 当前数组中正在处理的元素。

第一次调用时,若指定了初始值initialValue, 其值则为array[0], 否则为array[1]

  • currentIndex: 当前数组中正在处理的元素的索引。

若指定了初始值initialValue,则起始索引为0,否则从索引1开始

  • array: 用于遍历的数组

initialValue: 初始值,可选

返回值

使用回调函数遍历整个数组后的结果

注意点

  • reduce处理的元素在第一次调用callbackfn之前确定。

  • 在调用开始后,有元素追加到数组中也不会被callbackfn访问;

  • 如果现有的元素发生了变化,传递给callbackfn的将会是元素被reduce访问时的值(即发生变化后的值);

  • 在调用reduce开始后,尚未被访问的元素如果被删除,则其将不被reduce访问

使用示例

数组的累加&累乘

累加的初始值设置0, 累乘的初始值设置1

let arrNum = [1,2,3,4,5,6,7,8,9];
// 累加 sum: 45
let sum = arrNum.reduce((prev, current) => prev + current, 0);
// 累乘 multi: 362880
let multi = arrNum.reduce((prev, current) => prev * current, 1);

累加对象数组的某一个属性值

let arrObj = [
  {x1y2}, 
  {x2y4}, 
  {x3y8}, 
  {x4y16}
];

// 累加x  sumObjX: 10
let sumObjX = arrObj.reduce((prev, curr) => prev + curr.x, 0);

// 累加y  sumObjY: 30
let sumObjY = arrObj.reduce((prev, curr) => prev + curr.y, 0);

计算最大值&最小值

计算数组中的最大/最小值,比较的是数组中的所有元素,不需要传初始值

let arrNum = [1,2,3,4,5,6,7,8,9];
// 计算最大值  maxValue: 9
let maxValue = arrNum.reduce((prev, current) => current > prev ? current : prev);
// 计算最小值  minValue: 1
let minValue = arrNum.reduce((prev, current) => current < prev ? current : prev);

数组扁平化

// 场景1:
let arrMul = [[1,2], [3,4], [56 ,7]];
let flattenedArrMul = arrMul.reduce((prev, current) => {
 return prev.concat(current)
}, []);
// flattenedArrMul: [1, 2, 3, 4, 5, 6, 7]

// 场景2:
let arrMulN = [1, [2, [34]]];
// 多重嵌套的数组 采用递归 
function flatten(arr{
  return arr.reduce((prev, current) => {
    return prev.concat(Array.isArray(current) ? flatten(current) : current)
  }, [])
}
// [1, 2, 3, 4]
console.log(flatten(arrMulN));

数组去重

let myArray = ['a''b''a''b''c''e''e''c''d''d''d''d']
let myArrayWithNoDuplicates = myArray.reduce(function (prev, current{
  if (prev.indexOf(current) === -1) {
    prev.push(current)
  }
  return prev
}, [])
// myArrayWithNoDuplicates:
// ['a', 'b', 'c', 'e', 'd']

计算元素出现的次数

let names = ['Alice''Bob''Tiff''Bruce''Alice']
let countedNames = names.reduce((allNames, name) => {
  if (name in allNames) {
    allNames[name]++
  } else {
    allNames[name] = 1
  }
  return allNames
}, {}) 
// countedNames: 
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}

按某一属性值对object分类

let people = [
  { name'Alice'age21 },
  { name'Max'age20 },
  { name'Jane'age20 }
];

function groupBy (objArr, property{
  return objArr.reduce((acc, obj) => {
    // 属性值
   let key = obj[property]
    if (!acc[key]) {
      acc[key] = []
    } 
    acc[key].push(obj)
    return acc
  }, {})
}

let groupedPeople = groupBy(people, 'age')

// groupedPeople:
// {
//   20: [
//     { age: 20, name: 'Max'},
//     { age: 20, name: 'Jane'}
//   ],
//   21: [
//     { age: 21, name: 'Alice' }
//   ]
// }

替换mapfilter


const nums = [-1-2012013];
 
const doubledPositiveNumbers = nums.reduce((previousValue, currentValue) => {
  if (currentValue > 0) {
    const doubled = currentValue * 2;
    previousValue.push(doubled);
  }
  return previousValue;
}, []);

//doubledPositiveNumbers: [2, 4, 2, 6]

手写Reduce

思路分析

  • 原型链上添加函数

  • 入参有两个,回调函数callback及初始值initialValue

  • 检验逻辑

    当传入的回调函数不是函数时,抛出TypeError;

    当数组长度为0并且没有设置初始值时,抛出TypeError;

  • 回调函数有四个参数

  • initialValue指定与未指定的情况以下三个参数初始值不同

    previousValuecurrentValuecurrentIndex

代码

Array.prototype.myReduce = function(callback, initialValue{
  // 绑定this
  let arr = this;
  // 数组长度
  let len = arr.length;

  // TypeError
  if (typeof callback != 'function') {
    throw new TypeError( callback +' is not a function');
  }
  // TypeError
  if (len == 0 && typeof initialValue == 'undefined') {
    throw new TypeError('Reduce of empty array with no initial value');
  }

  // 初始值
  let previousValue, currentValue, currentIndex;
  if (typeof initialValue == 'undefined') {
    previousValue = arr[0];
    currentValue = arr[1];
    currentIndex = 1;
  } else {
    previousValue = initialValue;
    currentValue = arr[0];
    currentIndex = 0;
  }
  // 
  for (let i = currentIndex; i < arr.length; i++) {
    const currentItem = arr[i];
    previousValue = callback(previousValue, currentItem, i, arr)
  }

  return previousValue
}

测试一下

let people = [
  { name'Alice'age21 },
  { name'Max'age20 },
  { name'Jane'age20 }
];
  
function groupBy (objArr, property{
  // 采用手写的myReduce方法
  return objArr.myReduce((acc, obj) => {
    // 属性值
    let key = obj[property]
    if (!acc[key]) {
      acc[key] = []
    } 
    acc[key].push(obj)
    return acc
  }, {})
}

let groupedPeople = groupBy(people, 'age')
// 结果
// {
//   20: [
//     { age: 20, name: 'Max'},
//     { age: 20, name: 'Jane'}
//   ],
//   21: [
//     { age: 21, name: 'Alice' }
//   ]
// }

参考

  • 关于Reduce的详细使用及规范介绍:

MDN Reduce

ECMA-262 23.1.3.24 Array.prototype.reduce

分类:

前端

标签:

前端

作者介绍

山谷萤火虫
V1