以前总是习惯用forEach来遍历数组,看别人的代码总是使用reduce,所以学习一下reduce的大概用法。
reduce通过一个传入的函数,对数组的每一项从左往右进行累加,最终产生一个结果。
例如,把数组里1-100的元素相加
```javascript
// 初始化1-100的数组
let numbers = [...new Array(100).keys()];
// 计算数组元素之和
let sum = numbers.reduce((sum, cur) => sum + cur);
console.log(sum, "sum");// 4950
```
reduce使用语法
arr.reduce(callback(previousValue, currentValue[, index[, array]])[, initialValue])
reduce接收一个回调函数和一个初始值,它对数组的每一项进行处理,最后一次的回调函数返回值作为reduce的返回值。回调函数的参数如下:
-
previousValue是回调函数上一次的返回值。第一次执行的时候如果传入initialValue,则previousValue为initialValue的值,如果没有传入,则选择数组的第一个元素。(这里是数组的第一个元素,具体看下面的源码分析)
-
currentValue是数组遍历中正在处理的元素
-
index是currentValue 在数组中的索引。
-
array是调用reduce的数组。
reduce 源码解析
源码来自于mdn的Polyfill
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;
}
首先是参数校验。对调用reduce的数组进行非null/undefined判断,这里应该使用==,再对传入的callback进行函数类型的校验。
然后是对第一次调用时的previousValue和index的处理.
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++];
}
-
如果传入参数里传入了initialValue,则参数长度满足大于等于2(没有写==2,很细节),将initialValue赋值给previousValue,index为0
-
如果没有传入initialValue那么获取数组里的第一个元素,这里并没有使用o[0]来表示第一个元素而是使用了一个while循环来找到有下标的第一个元素。
// 这样的数组没有0-4的下标索引,所以第一个previousValue为6 let arr=new Array(5) arr[5]=6
-
之后使用while (k < len)来遍历数组的每一项元素,循环里再次使用(k in o)来排除empty元素。
这里学到了一个细节之处,new Array(5)构造的数组是没有0-4的索引下标的,所以reduce使用k < len && !(k in o)来排除数组中的empty元素。
reduce实战使用
-
将多个promise形成的数组按先后顺序执行
const f1 = () => new Promise(resolve => {
setTimeout(() => { console.log(new Date(), "new Date()"); console.log("加载第一份数据"); resolve(); }, 2000); }); const f2 = () => ( setTimeout(() => { new Promise(resolve => { console.log(new Date(), "new Date()"); console.log("加载第二份数据"); resolve(); }); }, 1000) ); const runPromiseInSequence = array => array.reduce((pre, cur) => pre.then(cur), Promise.resolve()); runPromiseInSequence([f1, f2]);
在实际开发过程中某些数据请求是有先后顺序,这个按顺序执行的runPromiseInSequence函数接收一个promise数组。核心是按顺序把函数放在Promise().then()里面来保证执行顺序。
-
功能型函数管道
const double = x => x + 2; const triple = x => x + 3;
const pipe = (...functions) => input => functions.reduce( (acc, fn) => fn(acc), input ); let doubleAndTriple = pipe(double,triple); let value = doubleAndTriple(1); console.log(value) //6
使用reduce封装多个函数的集合体,类似一个一个管道进行加工,依次执行每个函数。