js - 数组 - reduce 的用法

283 阅读4分钟

引入 mdn 的说法

reduce()  方法对数组中的每个元素按序执行一个由您提供的 reducer 函数 ,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被作为初始值 initialValue,迭代器将从第二个元素开始执行(索引为 1 而不是 0) reduce

reduce(callbackFn, initialValue)

callbackFn

  1. 第一个 参数 prev,如果没有 initialValue 的话,指向的是 数组的 第一项,如果存在的话,指向 initialValue
  2. 第二个 参数 accr,如果没有 initialValue 的话,指向的是 数组的 第二项,存在的话,指向 数组的第一项
  3. 第三个参数 currentIndex ,没有 initialValue,是从 0 开始,存在的话,从 1 开始
  4. 第四个参数 array ,需要遍历的数组

initialValue 初始值

example:

求数组所有值的和

let sum = [0, 1, 2, 3].reduce(function (previousValue, currentValue) {
  return previousValue + currentValue
}, 0)

累加对象数组里的值

要累加对象数组中包含的值,必须提供 initialValue,以便各个 item 正确通过你的函数。

let initialValue = 0
let sum = [{x: 1}, {x: 2}, {x: 3}].reduce(function (previousValue, currentValue) {
    return previousValue + currentValue.x
}, initialValue)

console.log(sum) // logs 6

将二维数组转化为一维

let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(previousValue, currentValue) {
    return previousValue.concat(currentValue)
  },
  []
)

计算数组中每个元素出现的次数

let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']

let countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++
  }
  else {
    allNames[name] = 1
  }
  return allNames
}, {})

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

// friends - an array of objects
// where object field "books" is a list of favorite books
let 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 - list which will contain all friends' books +
// additional list contained in initialValue
let allbooks = friends.reduce(function(previousValue, currentValue) {
  return [...previousValue, ...currentValue.books]
}, ['Alphabet'])

数组去重

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

console.log(myArrayWithNoDuplicates)

// 利用了 数组 的 sort 排序,[1,1,2,3,3]大小相同的数字会被排列在一起
// 利用这个特性 prev 的最后一个值 如果和 当前值 不等的话,才会被 push
myArray.sort().reduce((prev,cur)=>{
    if(prev.length == 0 || prev[prev.length-1] != cur){
        prev.push(cur)
    }
    return prev
},[])

使用 .reduce() 替换 .filter().map()

使用 Array.filter() 和 Array.map() 会遍历数组两次,而使用具有相同效果的 Array.reduce() 只需要遍历一次,这样做更加高效。(如果你喜欢 for 循环,你可用使用 Array.forEach() 以在一次遍历中实现过滤和映射数组)

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

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

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

按顺序运行 Promise

/**
 * 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) {
  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

用 reduce 实现 map

 map
 [1,2,3].map((item,index,arr){
  return item++
})
// map 接受一个 回调函数,需要 当前项 当前下标 和 当前数组
// 返回一个 新的数组 可以用 reduce 实现
// 接收一个 callback 和 this 指向
Array.prototype.myMap =function(callback,thisArg){
// this 代表的遍历的数组
// mapArray 是一个空数组不断的添加 callback 执行的值
    return this.reduce((mapArray,cur,index,arr){
        mapArray[index] = callback.call(thisArg,cur,index,arr)
        return myArray
    },[])
}
//
[1,2,3].myMap((item,index,arr){
  return item++
},this)

分割数组

function split(arr, target) {
  return arr.reduce((prev, cur) => {
    // 当长度 == target 的时候 prev 新开一个 二维数组 [...[cur]]
   prev[prev.length - 1].length == target ? prev.push([cur]) : prev[prev.length - 1].push(cur);
     return prev
  }, [[]])
}
console.log(split([1, 2, 3, 4, 5], 3));

stringifySearch

const stringifySearch = (search = {}) => {
 return Object.entries(search)
   .reduce(
     (t, v) => 
     // t 最开始是 ?,后来不断的拼接上原来数据
       `${t}${v[0]}=${encodeURIComponent(v[1])}&`,
     Object.keys(search).length ? "?" : "" // 初始字段
   )
   .replace(/&$/, "");
};
const search = stringifySearch({
 name: "fatfish",
 age: 100,
});
const link = `https://medium.com/${search}`;
console.log(link); // https://medium.com/?name=fatfish&age=100

pipe 管道

只有一项的时候,是不会执行 reduce函数,会返回当前值

    function add1(a) {
     return a + 1
   }
   function add2(a) {
     return a + 2
   }
   function add3(a) {
     return a + 3
   }


function pipe(...fns){
     // 从右到左执行
     // 1. a 是 add1,b 是 add2, 返回一个 (...args)=> add1(add2(...args))
     // 2. a 是 (...args)=> add1(add2(...args)), b 是 add3, (...args)=> add1(add2(add3(args)))
     
     // 经过试验,如果fns 只有一项,是不会执行的,直接返回 add1
     // if(fns.length ==1) return fns[0];
     console.log(fns);
     let res = fns.reduceRight((a,b)=>{
       return (...args)=> a(b(...args))
     });
     return res
}

let fn =  pipe(add1, add2, add3)
let res = fn("abcd")