JavaScript 深入数组方法之 reduce

60 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

数组方法有很多种,但是作为中高级前端开发工程师,如果仅仅只熟悉最基础的那几种,很显然是不够的,接下来我们就深入研究一下 reduce 方法

那么先让我们了解一下 reduce

reduce()  方法对数组中的每个元素按序执行一个 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
这个汇总的过程,我们也可以称之为 归并, 利用其遍历的特性, reduce 方法 接收两个参数,第一个是我们的回调函数,第二个参数是初始值, 初始值可以不传, 如果不传初始值, 回调函数中 四个参数 分别是 数组中的 第0项、数组中的第1项,下标(从1开始),原数组; 如果传了初始值,回调函数中的 四个参数 分别是 初始值,数组中的第0项,下标(从0开始)原数组。

  • 让我们先看一下小 demo
let arr = [5,2,5,6]
// 归并 value就是每次遍历的归并值,第一次value是数组的第0项, sum 相当于求和
let sum=arr.reduce(function(value,item,index,arr){
    console.log(value,item,index);
    return value+item;
})
// 下面我们传了初始值, 相当于 从 100 开始加,数组中的所有项求和 再+100
let sum2=arr.reduce(function(value,item,index,arr){
    console.log(value,item,index);
    return value+item;
},100)

Snipaste_2022-12-05_12-40-25.png

  • 下面我们自己手写一下 reduce 的简单实现, reduce 会跳过数组空位, 如 [5,4,,5] 这个数组中的第三项就是空位,数组空位我会在接下来的一篇重点调研,大家可以去看, 数组方法中 还有一个 reduceRight ,只是遍历是从后向前遍历, 实现上 只需要 for 循环反向遍历 i-- 即可。
// reduce 方法 (prev,item,index,arr) 默认值, 第一次循环开始,默认值不写 prev 是下标为 0 的数据, item 是下标为 1 的数据, index 是 1, 写了默认值, prev 就是默认值,  item 是下标为 0 的数据, index 是 0.
Array.prototype.myReduce = function(cb,init){
  let i = 0;
  if(init === undefined){
    init = this[0];
    i = 1
  }
  for(; i < this.length ; i++){
    if(!(i in this)) continue;
    init = cb(init,this[i],i,this);
  }
  return init
}

巧用 reduce

  • 计算数组中每个元素出现的次数
let arr = ['a','a','b','d','c','d']
let countName = arr.reduce((prev,item)=>{
  if(item in prev){
    prev[item]++
  }else{
    prev[item] = 1
  }
  return prev
},{})
  • 数组去重
let arr = ['a','a','b','d','c','d']
let myFilter = arr.reduce((prev,item)=>{
  if(!~prev.indexOf(item)){
    prev.push(item)
  }
  return prev
},[])
  • reduce 的归并特性,能够让我们实现大部分需求,而我们常用的数组方法,其也能够实现, 如 mapsomefilterevery等,下面咱们就来看看他的强大。
  • 实现 map , 但这里只是一个简单的实现, 真正的 map 不会跳过数组空位, 会保留空位,而 reduce 会跳过空位。
Array.prototype.myMap = function(cb,init){
  return this.reduce(function(mapArr,currentArr,index,arr){
    mapArr[index] = cb.call(init,currentArr,index,arr)
    return mapArr
  },[])
}
  • 实现 filter
let arr = ['a','a','b','d','c','d']
const filterArr = arr.reduce((prev,item)=>{
  if(item === 'a'){
    prev.push(item)
  }
  return prev
},[])
  • 实现 somevery , 这两个方法异曲同工, 都是返回 布尔值, 依据我们回调函数中的逻辑。
let arr=[2,3,4,5,6];
// some  默认返回 false , 空数组 返回 false
let bool=arr.reduce(function(value,item){
    if(item>3) value=true;
    return value;
},false)
// every  默认返回 true, 空数组 返回 true
let bool2=arr.reduce(function(value,item){
    if(item<=3) value=false;
    return value;
},true)
console.log(bool)

通过上面的示例,大家可以看到 reduce 可以实现我们业务当中大部分场景,其内部 归并的算法 也并么没有增加我们的性能消耗,可能我们业务中有时候需要两个循环,或者嵌套循环处理的业务,使用一个 reduce 就可以解决了,希望我们能一起用好 reduce~

在项目中的简单应用

  • 根据 cookie 获取 token , 这个项目我们使用了 ts, 如果 ts 看不懂的同学建议补习一下哦~ , 配合字符串的分割方法 split
// 获取返回的token
export function getCookie(name: string) {
  const cookies = document.cookie.split("; ");
  const cookieObj = cookies.reduce( (pre: {[name: string]: string}, next) => {
      const [ name, value ] = next.split("=");
      pre[name] = value;
      return pre;
  }, {});
  return name ? cookieObj[name] : cookieObj;
}
  • 对数据进行处理,这种可能比较常用了。
 const data = list.reduce((prevFilter: ListDataType[], item: ListDataResType) => {
  if (!item.count) return prevFilter;
  prevFilter.push({
    value: item.count,
    type: item.type,
    percents: item.value,
  });
  return prevFilter;
}, []);