当前位置:   article > 正文

有关 Array.prototype.reduce() 方法的详解

array.prototype.reduce()

Array.prototype.reduce()

reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值

reducer 函数接收 4 个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组)

您的 reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值

语法

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

参数

callback
执行数组中每个值 (如果没有提供 initialValue 则第一个值除外)的函数,包含四个参数:

accumulator
  累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue(见于下方)。

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

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

array 可选
  调用 reduce() 的数组

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

返回值

  函数累计处理的结果

描述

reduce 为数组中的每一个元素依次执行 callback 函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:

  • accumulator 累计器
  • currentValue 当前值
  • currentIndex 当前索引
  • array 数组

回调函数第一次执行时,accumulatorcurrentValue 的取值有两种情况:如果调用 reduce() 时提供了 initialValueaccumulator 取值为 initialValuecurrentValue 取数组中的第一个值;如果没有提供 initialValue,那么 accumulator 取数组中的第一个值,currentValue 取数组中的第二个值。

!注意:
  如果没有提供 initialValuereduce 会从索引 1 的地方开始执行 callback 方法,跳过第一个索引;如果提供 initialValue,从索引 0 开始。
  如果数组为空且没有提供 initialValue,会抛出 TypeError ;如果数组仅有一个元素(无论位置如何)并且没有提供 initialValue, 或者有提供 initialValue 但是数组为空,那么此唯一值将被返回并且 callback 不会被执行。

提供初始值通常更安全,正如下面的例子,如果没有提供 initialValue,则可能有四种输出:

	var maxCallback = ( acc, cur ) => Math.max( acc.x, cur.x );
		var maxCallback2 = ( max, cur ) => Math.max( max, cur );
		
		// reduce() 没有初始值
		[ { x: 2 }, { x: 22 }, { x: 42 } ].reduce( maxCallback ); // NaN
		[ { x: 2 }, { x: 22 }            ].reduce( maxCallback ); // 22
		[ { x: 2 }                       ].reduce( maxCallback ); // { x: 2 }
		[                                ].reduce( maxCallback ); // TypeError
		
		// map/reduce; 这是更好的方案,即使传入空数组或更大数组也可正常执行
		[ { x: 22 }, { x: 42 } ].map( el => el.x )
		                        .reduce( maxCallback2, -Infinity );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注:有关 map() 方法可参考 Array.prototype.map()

reduce() 如何运行

假如运行下段 reduce() 代码:

	[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array){
	  return accumulator + currentValue;
	});
  • 1
  • 2
  • 3

callback 被调用四次,每次调用的参数和返回值如下表:

callbackaccumulatorcurrentValuecurrentIndexarrayreturn value
first call011[0, 1, 2, 3, 4]1
second call122[0, 1, 2, 3, 4]3
third call333[0, 1, 2, 3, 4]6
fourth call644[0, 1, 2, 3, 4]10

reduce 返回的值将是最后一次回调返回值 10

你还可以使用箭头函数来代替完整的函数。 下面的代码将产生与上面的代码相同的输出:

	[0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr );
  • 1

如果你打算提供一个初始值作为 reduce() 方法的第二个参数,以下是运行过程及结果:

	[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
	    return accumulator + currentValue
	}, 10)
  • 1
  • 2
  • 3
callbackaccumulatorcurrentValuecurrentIndexarrayreturn value
first call1000[0, 1, 2, 3, 4]10
second call1011[0, 1, 2, 3, 4]11
third call1122[0, 1, 2, 3, 4]13
fourth call1333[0, 1, 2, 3, 4]16
fifth call1644[0, 1, 2, 3, 4]20

这种情况下 reduce() 返回的值是 20

例子(前五个较为常用)

1. 数组里所有值的和

	var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
	  return accumulator + currentValue;
	}, 0);
	// 和为 6
  • 1
  • 2
  • 3
  • 4

你也可以写成箭头函数的形式:

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

2. 累加对象数组里的值

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

	var initialValue = 0;
	var sum = [{x: 1}, {x:2}, {x:3}].reduce(function (accumulator, currentValue) {
	    return accumulator + currentValue.x;
	},initialValue)
	
	console.log(sum) // logs 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

你也可以写成箭头函数的形式:

	var initialValue = 0;
	var sum = [{x: 1}, {x:2}, {x:3}].reduce(
	    (accumulator, currentValue) => accumulator + currentValue.x
	    ,initialValue
	);
	
	console.log(sum) // logs 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3. 将二维数组转化为一维

	var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
	  function(a, b) {
	    return a.concat(b);
	  },
	  []
	);
	// flattened is [0, 1, 2, 3, 4, 5]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

你也可以写成箭头函数的形式:

	var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
	 ( acc, cur ) => acc.concat(cur),
	 []
	);
  • 1
  • 2
  • 3
  • 4

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

	var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
	
	var countedNames = names.reduce(function (allNames, name) {
	  if (name in allNames) {
	    allNames[name]++;
	  }
	  else {
	    allNames[name] = 1;
	  }
	  return allNames;
	}, {});
	// countedNames is:
	// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5. 按属性对object分类

	var people = [
	  { name: 'Alice', age: 21 },
	  { name: 'Max', age: 20 },
	  { name: 'Jane', age: 20 }
	];
	
	function groupBy(objectArray, property) {
	  return objectArray.reduce(function (acc, obj) {
	    var key = obj[property];
	    if (!acc[key]) {
	      acc[key] = [];
	    }
	    acc[key].push(obj);
	    return acc;
	  }, {});
	}
	
	var groupedPeople = groupBy(people, 'age');
	// groupedPeople is:
	// {
	//   20: [
	//     { name: 'Max', age: 20 },
	//     { name: 'Jane', age: 20 }
	//   ],
	//   21: [{ name: 'Alice', age: 21 }]
	// }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

	// friends - 对象数组
	// where object field "books" - list of favorite books
	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
	}];
	
	// allbooks - list which will contain all friends' books +
	// additional list contained in initialValue
	var allbooks = friends.reduce(function(prev, curr) {
	  return [...prev, ...curr.books];
	}, ['Alphabet']);
	
	// allbooks = [
	//   'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
	//   'Romeo and Juliet', 'The Lord of the Rings',
	//   'The Shining'
	// ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

7. 数组去重

注意: 如果你正在使用一个可以兼容 SetArray.from() 的环境, 你可以使用 let orderedArray = Array.from(new Set(myArray)); 来获得一个相同元素被移除的数组。

	let myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd']
	let myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
	  if (accumulator.indexOf(currentValue) === -1) {
	    accumulator.push(currentValue)
	  }
	  return accumulator
	}, [])
	
	console.log(myOrderedArray)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
	let arr = [1,2,1,2,3,5,4,5,3,4,4,4,4];
	let result = arr.sort().reduce((init, current) => {
	    if(init.length === 0 || init[init.length-1] !== current) {
	        init.push(current);
	    }
	    return init;
	}, []);
	console.log(result); //[1,2,3,4,5]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

8. 按顺序运行 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

9. 功能型函数管道

	// Building-blocks to use for composition
	const double = x => x + x;
	const triple = x => 3 * x;
	const quadruple = x => 4 * x;
	
	// Function composition enabling pipe functionality
	const pipe = (...functions) => input => functions.reduce(
	    (acc, fn) => fn(acc),
	    input
	);
	
	// Composed functions for multiplication of specific values
	const multiply6 = pipe(double, triple);
	const multiply9 = pipe(triple, triple);
	const multiply16 = pipe(quadruple, quadruple);
	const multiply24 = pipe(double, triple, quadruple);
	
	// Usage
	multiply6(6); // 36
	multiply9(9); // 81
	multiply16(16); // 256
	multiply24(10); // 240
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

10. 使用 reduce 实现 map

	if (!Array.prototype.mapUsingReduce) {
	  Array.prototype.mapUsingReduce = function(callback, thisArg) {
	    return this.reduce(function(mappedArray, currentValue, index, array) {
	      mappedArray[index] = callback.call(thisArg, currentValue, index, array)
	      return mappedArray
	    }, [])
	  }
	}
	
	[1, 2, , 3].mapUsingReduce(
	  (currentValue, index, array) => currentValue + index + array.length
	) // [5, 7, , 10]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Polyfill

	// Production steps of ECMA-262, Edition 5, 15.4.4.21
	// Reference: http://es5.github.io/#x15.4.4.21
	// https://tc39.github.io/ecma262/#sec-array.prototype.reduce
	if (!Array.prototype.reduce) {
	  Object.defineProperty(Array.prototype, 'reduce', {
	    value: function(callback /*, initialValue*/) {
	      if (this === null) {
	        throw new TypeError( 'Array.prototype.reduce ' +
	          'called on null or undefined' );
	      }
	      if (typeof callback !== 'function') {
	        throw new TypeError( callback +
	          ' is not a function');
	      }
	
	      // 1. Let O be ? ToObject(this value).
	      var o = Object(this);
	
	      // 2. Let len be ? ToLength(? Get(O, "length")).
	      var len = o.length >>> 0;
	
	      // Steps 3, 4, 5, 6, 7
	      var k = 0;
	      var value;
	
	      if (arguments.length >= 2) {
	        value = arguments[1];
	      } else {
	        while (k < len && !(k in o)) {
	          k++;
	        }
	
	        // 3. If len is 0 and initialValue is not present,
	        //    throw a TypeError exception.
	        if (k >= len) {
	          throw new TypeError( 'Reduce of empty array ' +
	            'with no initial value' );
	        }
	        value = o[k++];
	      }
	
	      // 8. Repeat, while k < len
	      while (k < len) {
	        // a. Let Pk be ! ToString(k).
	        // b. Let kPresent be ? HasProperty(O, Pk).
	        // c. If kPresent is true, then
	        //    i.  Let kValue be ? Get(O, Pk).
	        //    ii. Let accumulator be ? Call(
	        //          callbackfn, undefined,
	        //          « accumulator, kValue, k, O »).
	        if (k in o) {
	          value = callback(value, o[k], k, o);
	        }
	
	        // d. Increase k by 1.
	        k++;
	      }
	
	      // 9. Return accumulator.
	      return value;
	    }
	  });
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

如果您需要兼容不支持 Object.definePropertyJavaScript 引擎,那么最好不要 polyfill Array.prototype 方法,因为你无法使其成为不可枚举的。


转载自 MDN Web Docs 的文章 Array.prototype.reduce()

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/901715
推荐阅读
相关标签
  

闽ICP备14008679号