Array.prototype.reduce方法

133 阅读4分钟

reduce 方法

reduce方法主要是将数组中每个元素(但是不包括被删除以及从未赋值的元素)都执行一遍reducer函数,并将结果通过一个值返回。也就是说,reduce的返回值是单个值。

reduce 方法语法


arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

reducer 函数(callback)

根据MDN,reducer函数接收4个参数:

  • 累加器 Accumulator (acc)
  • 当前值 Current Value (cur)
  • 当前索引 Current Index (idx), 可选
  • 源数组 Source Array (src), 可选 reducer函数的返回值被分配给累加器,该返回值会在每次迭代中被记住,最终成为单个返回值。

initialValue

第一次调用callback函数的第一个参数(累加器)的值,如果没有提供初始值,则使用数组中第一个元素的值。如果数组为空,且没有初始值,调用reduce方法会报错,抛出TypeError错误。

返回值

函数累计处理的结果

一些注意点:

1、第一个执行回调函数的时候,acc 和 cur 的取值有两种情况:

  • 有初始值:acc取值为默认值,cur取值为数组第一个元素,从索引0开始执行callback
  • 无初始值:acc取值为数组第一个元素,cur取值为数组第二个元素,从索引1开始执行callback 2、数组为空的情况:
  • 有初始值:不会执行回调函数,返回值为初始值
  • 无初始值: 报错,抛出TypeError错误 3、数组长度为1:
  • 有初始值:acc取值为默认值,cur取值为数组第一个元素
  • 无初始值:不执行回调函数,acc取值为数组第一个元素,返回值为数组第一个元素 4、reduce方法会自动忽略数组中从未赋值的元素,比如:

let arr =[,]
console.log(arr.length);  // 1
console.log(arr); // [empty]
let filterArr = [];

// 通过hasOwnProperty方法过滤数据
for(let i = 0; i < arr.length; i++) {
  if (Object.prototype.hasOwnProperty(arr, i)) {
    filterArr.push(i)
  }
}
console.log(filterArr);  // []
console.log(filterArr.length); // 0

reduce实现

reduce接收两个参数:

  • @param { Function } callback
  • @param { Any } 可选的initialValue callback 接收前面介绍的四个参数
  • @param acc
  • @param cur
  • @param idx
  • @param array

    Array.prototype.myReduce = function(fn, initialValue) {
      // 判断调用对象是否为Array
      if (Object.prototype.toString.call(this) !== '[object Array]') {
        throw new SyntaxError('Unexpected token \'\.\'')
      }

      // 判断fn是否为函数
      if (typeof fn === 'object' && fn !== null) {
        throw new TypeError('#<Object> is not a function')
      } else if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`)
      }

      // 过滤数组中从未赋值的元素
      // 比如[,].length = 1, 过滤后数组长度为0
      // console.log([,])  // [empty]
      let filterArr = [];
      for(let i = 0; i < this.length; i++) {
        if (Object.prototype.hasOwnProperty.call(this, i)) {
          filterArr.push(this[i])
        }
      }

      // 数组为空且没有初始值,抛出异常
      if (!filterArr.length && arguments.length === 1 ) {
        throw new TypeError('Reduce of empty array with no initial value')
      }

      // 空数组有初始值,返回初始值
      if (!filterArr.length && arguments.length > 1 ) {
        arguments[1]
        return arguments[1]
      }

      // 数组长度为1,没有初始值,返回数组第一个元素
      if (filterArr.length === 1 && arguments.length === 1) {
        return filterArr[0]
      }

      for(let i = 0; i < filterArr.length; i++) {
        if (typeof initialValue === 'undefined') {
          initialValue = fn(filterArr[i], filterArr[i+1], i+1, filterArr)
          ++i;
        } else {
          initialValue = fn(initialValue, filterArr[i], i, filterArr)
        }
      }
      return initialValue
    }

    let test1 = [1,2,3].reduce((acc, cur) => acc + cur);
    let test2 = [1,2,3].myReduce((acc, cur) => acc + cur);
    console.log('reduce', test1)  // reduce 6
    console.log('myReduce', test2) // myReduce 6

    let test3 = [].reduce((acc, cur) => acc, null);
    let test4 = [].myReduce((acc, cur) => acc + cur, null);
    console.log('reduce', test3) // reduce null
    console.log('myReduce', test4) // myReduce null

    let test5 = [1].reduce((acc, cur) => acc);
    let test6 = [1].myReduce((acc, cur) => acc);
    console.log('reduce', test5) // reduce 1
    console.log('myReduce', test6) // myReduce 1
    
    let test7 = ['1',null,undefined,,3,4].reduce((acc, cur) => acc + cur);
    let test8 = ['1',null,undefined,,3,4].myReduce((acc, cur) => acc + cur);
    console.log('reduce', test7) // reduce 1nullundefined34
    console.log('myReduce', test8) // myReduce 1nullundefined34

    let test9 = [].reduce((acc, cur) => acc + cur); // TypeError,后面代码不执行
    let test10 = [].myReduce((acc, cur) => acc + cur);
    console.log('reduce', test9)  
    console.log('myReduce', test10)
   
 

reduce应用例子

1、求和


let total = [1,2,3,4].reduce((acc, cur) => acc + cur, 0)

2、flatten 扁平化数组

// concat 方法可以接收数组或者值
// ['algh', 'happy'].concat(1, [2, 3]);  
// 输出:['algh', 'happy', 1, 2, 3]

const flatten = list => 
	list.reduce((acc,cur) => 
    	acc.concat(Array.isArray(cur) ? flatten(cur) : cur)
    ,[])
let test11 = flatten([1,2,[3,4,5,[6,[7,8,[9]]]],10,11])
console.log(test11)
    

3、将一个数组转换为对象

/*
*	[
*		{ name: 'lili' },
*		{ age: 21 }
* 	]
*  
* 	{ name: 'lili', age: 21 }
*/
const arrToObj = list => 
	list.reduce((acc, cur) => {
    	// Object.entries(cur): [["name", "lili"]]
        // pair: ["name", "lili"]
    	const [pair] = Object.entries(cur); 
        const [k, v] = pair;
        acc[k] = v;
        return acc;
    }, {})
const test12 = arrToObj([{name: 'lili'},{age: 20}])
console.log(test12) // {name: 'lili', age: 20}


4、获取对象的值


const getObj = (obj, path) => {
  const pathArr = path.split('.');
  return pathArr.reduce((acc, cur) => acc ? acc[cur] : null, obj)
}
let test13 = getObj({ b: { c: { d: 2 }}}, 'b.c.d');
console.log(test13)  // 2
	

5、实现map

// 错误的示例
if (!Array.prototype.myMap) {
	Array.prototype.myMap = (fn, thisArg) => // 这里用箭头函数,会导致this指向window,应改为普通函数
    	this.reduce((acc, cur, index, arr) => {
        	acc[index] = fn.call(thisArg, cur, index, arr)
            return acc
        },[])
    
}

// 所以在实现某个原型方法的时候一定要用普通函数,箭头函数的this值绑定会导致不可预估的bug
// 正确的示例
if (!Array.prototype.myMap) {
  Array.prototype.myMap = function (fn, thisArg) {
    return this.reduce((acc, cur, index, arr) => {
        acc[index] = fn.call(thisArg, cur, index, arr)
          return acc
    },[])
  }

}
let test14 = [1,2,3].myMap((item) => item +1 )
console.log(test14)
 

6、数组去重

const array = [1,2,1,2,3,5,4,5,3,4,4,4,4];
const newArray = array.sort().reduce((acc, cur) => {
	if (acc.indexOf(cur) === -1) {
    	acc.push(cur)
    }
    return acc;
}, [])

const newArr = Array.from(new Set(array));
console.log(newArray, newArr) // 两个[1,2,3,4,5]