Array.prototype.reduce
归并类方法 【MDN Docs】
reduce() 方法接收一个函数作为 累加器(accumulator)
,数组中的每个值(从左到右)开始缩减,最终为一个值。
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,
reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
接受四个参数: 上一次回调函数的返回值(或者初始值)
, 当前元素值
, 当前索引
, 调用 reduce 的数组
。
回调函数第一次执行时,accumulator
和currentValue
的取值有两种情况:
- 如果调用
reduce()
时提供了initialValue
,accumulator
取值为initialValue
,currentValue
取数组中的第一个值; - 如果没有提供
initialValue
,那么accumulator
取数组中的第一个值,currentValue
取数组中的第二个值。
所以,如果没有提供 initialValue
,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。
如果提供 initialValue
,从索引0开始。
如果数组为空且没有提供 initialValue
,会抛出TypeError 。
如果数组仅有一个元素(无论位置如何)并且没有提供 initialValue
, 或者有提供 initialValue
但是数组为空,那么此唯一值将被返回并且callback不会被执行。
一般来说,提供初始值通常更安全。
constmaxCallback= (acc, cur) => { console.log(acc, cur); return { x: Math.max(acc.x, cur.x) }; }; constmaxCallback1= (acc, cur) => { console.log(acc, cur); returnMath.max(acc.x, cur.x); }; constmaxCallback2= (acc, cur) => { console.log(acc, cur); returnMath.max(acc, cur); }; // reduce() 没有初始值// 注意分配给累加器的返回值的形式[{ x: 2 }, { x: 22 }, { x: 42 }].reduce(maxCallback1); // NaN[{ x: 2 }, { x: 22 }].reduce(maxCallback1); // 22[{ x: 2 }].reduce(maxCallback1); // { x: 2 }[].reduce(maxCallback1); // Uncaught TypeError: Reduce of empty array with no initial value// map/reduce; 这是更好的方案,即使传入空数组或更大数组也可正常执行[{ x: 22 }, { x: 42 }].map((el) =>el.x).reduce(maxCallback2, -Infinity);
1 求和
constsumlist= [1, 3, 5, 7, 9, 11, 13]; constinitVal=0; // 第二次循环方法中prev的值,是第一次循环方法返回的结果。functionitercb(prev, curVal, curIndex, arr) { // console.log("先前值:", prev);// console.log("累加值:", curVal, ", 下标:", curIndex);returnprev+curVal; } consttotal=sumlist.reduce(itercb, initVal); console.log("sum total:", total);
2 滤重
constrepeatList= ["a", "aa", "aaa", "a", "a", "aa", "aaa", "aaaa", "aaa"]; functioncbRepeat(prev, curVal, curIndex) { if (prev.includes(curVal)) { // 若包含,则不添加returnprev; } else { // 添加return [prev, curVal]; } } constresult=repeatList.reduce(cbRepeat, []); console.log("result:", result); // ['a', 'aa', 'aaa', 'aaaa']// anotherconstarr= [1, 2, 3, 4, 4, 1]; constnewArr=arr.reduce((prev, cur) => { if (prev.indexOf(cur) ===-1) { returnprev.concat(cur); } else { returnprev; } }, []); console.log(newArr); // [1, 2, 3, 4]
各滤重方法的性能疑问
constmyArray= ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"]; console.time("indexOf"); letmyOrderedArray=myArray.reduce(function (accumulator, currentValue) { if (accumulator.indexOf(currentValue) ===-1) { accumulator.push(currentValue); } returnaccumulator; }, []); console.timeEnd("indexOf"); // indexOf: 0.02685546875 msconsole.log(myOrderedArray); console.time("sort"); letresult=myArray.sort().reduce((init, current) => { if (init.length===0||init[init.length-1] !==current) { init.push(current); } returninit; }, []); console.timeEnd("sort"); // sort: 0.06787109375 msconsole.log(result); // [1,2,3,4,5]console.time("set"); letorderedArray=Array.from(newSet(myArray)); console.timeEnd("set"); // set: 0.006103515625 msconsole.log(orderedArray);
3 计数
constnames= ["Alice", "Bob", "Tiff", "Bruce", "Alice"]; // {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}constnameNum=names.reduce((prev, cur) => { // in 如果指定的属性在指定的对象或其原型链中,返回true// in 右操作数必须是一个对象值,可以是 new String(),但不能是 '',删除delete和赋值undefined是不一样的结果if (curinprev) { prev[cur]++; } else { prev[cur] =1; } returnprev; }, {}); console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
in运算符用来判断对象是否拥有给定属性。
类比includes,判断数组是否拥有给定值。
for ... in & for ... of
constarr= ["a", "b", "c"]; constobj= { a: "aaa", b: "bbb", c: "ccc" }; for (letiinarr) { console.log(i); // 下标,索引值 0,1,2} for (letiofarr) { console.log(i); // 元素值 a,b,c} for (letiinobj) { console.log(i); // name,非value a,b,c} for (letiofobj) { console.log(i); // 报错,obj is not iterable} // 总结:// 对象只能用 for in 遍历,数组可以用 for in 和 for of 遍历// for in 遍历能获取 index 或 name,for of 遍历能拿到数组元素值// 只能foa 不能foo// fia fio 皆可,都是获取索引(index或name)
4 对象里的属性求和
varresult= [ { subject: "math", score: 10, }, { subject: "chinese", score: 20, }, { subject: "english", score: 30, }, ]; varsum=result.reduce(function (prev, cur) { returnprev+cur.score; }, 0); console.log(sum); // 60
5 多维数组转一维
constarr= [ [1, 2], [ [3, 4, [5, 6, 7], 8], [9, 10], ], 11, [12, 13, [14, 15]], ]; constflttenArr= (arr) => { returnarr.reduce((prev, cur) => { returnprev.concat(Array.isArray(cur) ?flttenArr(cur) : cur); }, []); }; flttenArr(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
6 按顺序运行promise
functionrunPromiseInSequence(arr, input) { returnarr.reduce((promiseChain, currentFunction) => { /* * 1、Promise {<fulfilled>: 10} * 2、Promise {<fulfilled>: 50} * 3、Promise {<fulfilled>: 100} * 4、Promise {<fulfilled>: 300} */console.log("acc:", promiseChain); /* * 1、p1 * 2、p2 * 3、f3 * 4、p4 */console.log("cur:", currentFunction); returnpromiseChain.then(currentFunction); }, Promise.resolve(input)); } // promise function 1functionp1(a) { returnnewPromise((resolve, reject) => { resolve(a*5); }); } // promise function 2functionp2(a) { returnnewPromise((resolve, reject) => { resolve(a*2); }); } // function 3 - will be wrapped in a resolved promise by .then()functionf3(a) { returna*3; } // promise function 4functionp4(a) { returnnewPromise((resolve, reject) => { resolve(a*4); }); } constpromiseArr= [p1, p2, f3, p4]; runPromiseInSequence(promiseArr, 10).then(console.log); // 1200
7 管道运算
constdouble= (x) =>x+x; consttriple= (x) =>x*3; constquardple= (x) =>x*4; constpipe= (functions) => (initVal) =>functions.reduce((acc, cur) => { returncur(acc); }, initVal); constmultiply4=pipe(double, double); constmultiply6=pipe(double, triple); constmultiply8=pipe(double, quardple); constmultiply9=pipe(triple, triple); constmultiply12=pipe(triple, quardple); constmultiply16=pipe(quardple, quardple); constmultiply24=pipe(double, triple, quardple); multiply4(10); multiply6(10); multiply8(10); multiply9(10); multiply12(10); multiply16(10); multiply24(10);
利用reduce实现map
constarr= [1, 3, 5, 7, 9]; constnewMappedArr=arr.map((item) =>item*item); console.log(newMappedArr); // [1, 9, 25, 49, 81]// thisArg可选 执行 callback 函数时值被用作this。// 不能使用箭头函数,没有thisArray.prototype.mapByReduce=function (cb, thisArg) { returnthis.reduce((acc, cur, index, arr) => { acc[index] =cb.call(thisArg, cur, index, arr); returnacc; }, []); }; // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/mapconstnewMRArr=arr.mapByReduce((item) =>item*3); console.log(newMRArr);
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.reduceif (!Array.prototype.reducee) { // 往 Array.prototype 对象上定义reduce属性,可被调用Object.defineProperty(Array.prototype, "reducee", { // 其值为函数value: function (callback/*, initialValue*/) { // this 为 reduce 方法的调用者,即某个数组if (this===null) { thrownewTypeError( "Array.prototype.reduce "+"called on null or undefined" ); } if (typeofcallback!=="function") { thrownewTypeError(callback+" is not a function"); } // 1. Let O be ? ToObject(this value).// Object 构造函数将给定的值包装为一个新对象。this为null或undefined时,o={}varo=Object(this); // 2. Let len be ? ToLength(? Get(O, "length")).// >>> 按位无符号右移运算符varlen=o.length>>>0; // 保证是个数字,可以将结果undefined变为0// Steps 3, 4, 5, 6, 7vark=0; // 即下标indexvarvalue; // 累加器,每次迭代的终值// 参数长度大于等于2if (arguments.length>=2) { // 参数列表第一个是回调函数,第二个是 初始值 initialValuevalue=arguments[1]; } else { // 正常有值数组,len>0,对象o中不存在k索引 ??? 排除无效的o属性while (k<len&&!(kino)) { k++; } // 3. If len is 0 and initialValue is not present,// throw a TypeError exception.// 空数组 & 无初始值if (k>=len) { thrownewTypeError( "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 (kino) { // 记得我们给的callback里,函数最后要return,不然value就拿不到值了value=callback(value, o[k], k, o); } // d. Increase k by 1.k++; } // 9. Return accumulator.returnvalue; }, }); }