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不會被執行。
一般來說,提供初始值通常更安全。
const maxCallback = (acc, cur) => {
console.log(acc, cur);
return { x: Math.max(acc.x, cur.x) };
};
const maxCallback1 = (acc, cur) => {
console.log(acc, cur);
return Math.max(acc.x, cur.x);
};
const maxCallback2 = (acc, cur) => {
console.log(acc, cur);
return Math.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 求和
const sumlist = [1, 3, 5, 7, 9, 11, 13];
const initVal = 0;
// 第二次循環方法中prev的值,是第一次循環方法傳回的結果。
function itercb(prev, curVal, curIndex, arr) {
// console.log("先前值:", prev);
// console.log("累加值:", curVal, ", 下标:", curIndex);
return prev + curVal;
}
const total = sumlist.reduce(itercb, initVal);
console.log("sum total:", total);
2 濾重
const repeatList = ["a", "aa", "aaa", "a", "a", "aa", "aaa", "aaaa", "aaa"];
function cbRepeat(prev, curVal, curIndex) {
if (prev.includes(curVal)) {
// 若包含,則不添加
return prev;
} else {
// 添加
return [...prev, curVal];
}
}
const result = repeatList.reduce(cbRepeat, []);
console.log("result:", result); // ['a', 'aa', 'aaa', 'aaaa']
// another
const arr = [1, 2, 3, 4, 4, 1];
const newArr = arr.reduce((prev, cur) => {
if (prev.indexOf(cur) === -1) {
return prev.concat(cur);
} else {
return prev;
}
}, []);
console.log(newArr); // [1, 2, 3, 4]
各濾重方法的性能疑問
const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
console.time("indexOf");
let myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
if (accumulator.indexOf(currentValue) === -1) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
console.timeEnd("indexOf"); // indexOf: 0.02685546875 ms
console.log(myOrderedArray);
console.time("sort");
let result = myArray.sort().reduce((init, current) => {
if (init.length === 0 || init[init.length - 1] !== current) {
init.push(current);
}
return init;
}, []);
console.timeEnd("sort"); // sort: 0.06787109375 ms
console.log(result); // [1,2,3,4,5]
console.time("set");
let orderedArray = Array.from(new Set(myArray));
console.timeEnd("set"); // set: 0.006103515625 ms
console.log(orderedArray);
3 計數
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
const nameNum = names.reduce((prev, cur) => {
// in 如果指定的屬性在指定的對象或其原型鍊中,傳回true
// in 右操作數必須是一個對象值,可以是 new String(),但不能是 '',删除delete和指派undefined是不一樣的結果
if (cur in prev) {
prev[cur]++;
} else {
prev[cur] = 1;
}
return prev;
}, {});
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
【in 操作符】 in運算符用來判斷對象是否擁有給定屬性。
類比includes,判斷數組是否擁有給定值。
【MDN 表達式和運算符】for ... in & for ... of
const arr = ["a", "b", "c"];
const obj = { a: "aaa", b: "bbb", c: "ccc" };
for (let i in arr) {
console.log(i); // 下标,索引值 0,1,2
}
for (let i of arr) {
console.log(i); // 元素值 a,b,c
}
for (let i in obj) {
console.log(i); // name,非value a,b,c
}
for (let i of obj) {
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 對象裡的屬性求和
var result = [
{
subject: "math",
score: 10,
},
{
subject: "chinese",
score: 20,
},
{
subject: "english",
score: 30,
},
];
var sum = result.reduce(function (prev, cur) {
return prev + cur.score;
}, 0);
console.log(sum); // 60
5 多元數組轉一維
const arr = [
[1, 2],
[
[3, 4, [5, 6, 7], 8],
[9, 10],
],
11,
[12, 13, [14, 15]],
];
const flttenArr = (arr) => {
return arr.reduce((prev, cur) => {
return prev.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
function runPromiseInSequence(arr, input) {
return arr.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);
return 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
7 管道運算
const double = (x) => x + x;
const triple = (x) => x * 3;
const quardple = (x) => x * 4;
const pipe =
(...functions) =>
(initVal) =>
functions.reduce((acc, cur) => {
return cur(acc);
}, initVal);
const multiply4 = pipe(double, double);
const multiply6 = pipe(double, triple);
const multiply8 = pipe(double, quardple);
const multiply9 = pipe(triple, triple);
const multiply12 = pipe(triple, quardple);
const multiply16 = pipe(quardple, quardple);
const multiply24 = pipe(double, triple, quardple);
multiply4(10);
multiply6(10);
multiply8(10);
multiply9(10);
multiply12(10);
multiply16(10);
multiply24(10);
利用reduce實作map
const arr = [1, 3, 5, 7, 9];
const newMappedArr = arr.map((item) => item * item);
console.log(newMappedArr); // [1, 9, 25, 49, 81]
// thisArg可選 執行 callback 函數時值被用作this。
// 不能使用箭頭函數,沒有this
Array.prototype.mapByReduce = function (cb, thisArg) {
return this.reduce((acc, cur, index, arr) => {
acc[index] = cb.call(thisArg, cur, index, arr);
return acc;
}, []);
};
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const newMRArr = 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.reduce
if (!Array.prototype.reducee) {
// 往 Array.prototype 對象上定義reduce屬性,可被調用
Object.defineProperty(Array.prototype, "reducee", {
// 其值為函數
value: function (callback /*, initialValue*/) {
// this 為 reduce 方法的調用者,即某個數組
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).
// Object 構造函數将給定的值包裝為一個新對象。this為null或undefined時,o={}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
// >>> 按位無符号右移運算符
var len = o.length >>> 0; // 保證是個數字,可以将結果undefined變為0
// Steps 3, 4, 5, 6, 7
var k = 0; // 即下标index
var value; // 累加器,每次疊代的終值
// 參數長度大于等于2
if (arguments.length >= 2) {
// 參數清單第一個是回調函數,第二個是 初始值 initialValue
value = arguments[1];
} else {
// 正常有值數組,len>0,對象o中不存在k索引 ??? 排除無效的o屬性
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) {
// 記得我們給的callback裡,函數最後要return,不然value就拿不到值了
value = callback(value, o[k], k, o);
}
// d. Increase k by 1.
k++;
}
// 9. Return accumulator.
return value;
},
});
}