面试官 : js的reduce方法你真的会用吗?

229 阅读5分钟

一、概述

reduce在我们的工作中使用的场景非常广泛,简直可以说是数组方法的万金油,熟练使用reduce绝对是可以让你在提升效率中事半功倍噢~

二、reduce的定义

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

参数:

  • callback执行数组中每个值 (如果没有提供 initialValue则第一个值除外)的函数,包含四个参数:
    • accumulator 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue(见于下方)。

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

    • index 可选 数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。

    • array可选 调用reduce()的数组

  • initialValue可选 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

使用例子:

[0, 1, 2, 3, 4].reduce((prev, curr) => {return prev + curr},[]); //01234
[0, 1, 2, 3, 4].reduce((prev, curr) => {return prev + curr},0); // 10

三、reduce的九个常见使用场景

1、数组求和

let arr = [1,2,3,4,5,6,7];
let result = arr.reduce((prev,next)=> prev+next);
console.log(result);

2、遍历累加数组对象里面的值

let arr = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
let result = arr.reduce((prev, next) => prev + next.value, 0);
console.log(result);

3、将二维数组转为一维数组

let arr = [[1,2],[3,4],[5,6]];
let result = arr.reduce((pre,next)=>{
    return pre.concat(next)},[]
)
console.log(result) // [1,2,3,4,5,6]

4、计算每个元素出现的次数

let arr = ["a", "b", "a", "c", "a", "d", "c"];
  let result = arr.reduce((pre, next) => {
    if (next in pre) {
      pre[next]++;
    } else {
      pre[next] = 1;
    }
    return pre;
  }, {});
  console.log(result); // {a:3,b:1,c:2,d:1}

5、按指定的属性去对object进行分类

let obj = [
    {
      name: "ronald",
      age: 30
    },
    {
      name: "alice",
      age: 31
    },
    {
      name: "tommie",
      age: 30
    },
  ];
  function getDataBySort(obj, property) {
    if (obj.length == 0) return;
    return obj.reduce((pre, next) => {
      let key = next[property]; //获取指定property的值当做键
      if (!pre[key]) {
        pre[key] = []; //如果对应的键值不存在,则创建一个,并且值设为空数组。
      }
      pre[key].push(next); //如果有对应的键值,则直接插入到对应的键名中。

      return pre;
    }, {});
  }
  console.log(getDataBySort(obj, "age")); // 
  //获得的答案是:
    { 30 : [
            {name: "ronald",age: 30},
            {name:"tommie",age: 30}
        ],
      31: [
            {name:'alice',age :31}
        ]
    }
      

6、善用扩展运算符和initialValue绑定包含在对象数组中的数组

//取出所有的book,并且放在同一个array里
var 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
}];

let result = friends.reduce((pre,next)=>{
    return pre.concat(...next.books);
},[])
console.log(result)

7、数据去重

其实使用es6的新语法更容易实现,使用Array.from(new Set(arr));

let arr = [1,2,3,1,3,4,5,3,4];
console.log(Array.from(new Set(arr)));

使用reduce去实现

let arr = [1,2,3,1,3,4,5,3,4];
let result = arr.reduce((pre,next)=>{
    if(!pre.includes(next)){
        pre.push(next)
    }
    return pre
},[])
console.log(result)

8、按顺序调用promise

这里拿的是mdn里的例子

/**
 * Runs promises from array of functions that can return promises
 * in chained manner
 *
 * @param {array} arr - promise arr
 * @return {Object} promise object
 */
 
function runPromiseInSequence(arr, input) {
   //这里传入的初始参数是一个promise对象,传入了10,所以promiseChain = Promise.resove(10), 
  //currentFunction 就是p1方法,接收到的参数是10,
  //执行完 p1 方法,又返回了一个promise对象,即Promise.resolve(10*5),以此类推。
  return arr.reduce(
    (promiseChain, currentFunction) => 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);
  });
}

// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
 return a * 3;
}

// promise function 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

9、功能性管道函数

const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;

//管道函数
const pipe = (...functions) {
    return function(input){
        return functions.reduce((pre,fn)=> fn(pre),input)
    }
}

//上面方法的解释:
// 用展开符...functions把传入的两个方法收集起来成为一个数组;
// 用高阶函数即返回一个函数的方式获取参数;
// return functions.reduce((pre,fn)) 可以理解为[fn1,fn2].reduce((pre,fn));
// 执行fn(pre)获得方法的值返回当做下一个pre的值,继续执行fn(pre)。


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方法第一个函数一定是一个函数,第二个参数是初始值(可不传)。

Array.prototype.reduceRewrite = function(fn,prev){
    //先判断传进来的第一个参数是否为一个函数
    if(typeof fn !== 'function'){
        return new Error('the first args must be a function');
    }
    let arr = this;
    let i = 0;
    
    //判断一下是否传入了初始值,有传的话,从第0个参数开始循环,不传的话,以第一个参数为初始值。
    if(typeof prev == 'undefined'){
        i = 1;
        prev = arr[0];
    }
    
    for(;i<arr.length;i++){
        prev = fn(prev,arr[i],i,arr);
    }
    return prev;
    
}

五、用reduce重写一个map函数

先看看map函数的用法,map会传入一个回调函数当做参数做循环,最后返回的是一个数组。

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

再复习下reduce的用法

[...].reduce((pre,next,idx,arr)=>{
    pre // 前面累加的值
    next // 数组中正在处理的数
    idx // 当前项的索引
    arr // 当前遍历的数组
},initValue)

这个时候我们开始重写map方法。

Array.prototype.reducerMap = function (fn, callbackThis) {
    //如果this没有指定默认指向window,避免报错
    let callThis = callbackThis || window;
    //因为map最终是返回一个数组,所以先初始化一个数组
    let res = [];
    //这里的this指向arr数组,然后调用reduce方法
    this.reduce((pre, next, ids, arr) => {
        //把reduce的四个参数用call方法绑定回fn里插入数组,因为用了call方法,所以会执行里面的函数,
        //即下面传进来的(item,index)=>{return item > 3}的这个方法,
        //最后把每一项的执行结果push进res中
        return res.push(fn.call(callThis, next, ids, arr));
    }, []);
    return res;
  };

  let arr = [1, 2, 3, 4, 5];

  let Arr = arr.reducerMap((item, index) => {
    return item > 3;
  });

  console.log(Arr); //[false,false,false,true,true]

上面就是reduce的常用场景啦,其实它还有很多场景值得我们去探索,善用这个方法,一定能提升我们的编程能力,继续加油,天道酬勤。