首頁>技術>

reduce作為ES5新增的常規陣列方法之一,對比forEach 、filter和map,在實際使用上好像有些被忽略,發現身邊的人極少用它,導致這個如此強大的方法被逐漸埋沒。

如果經常使用reduce,怎麼可能放過如此好用的它呢!我還是得把他從塵土中取出來擦乾淨,奉上它的高階用法給大家。一個如此好用的方法不應該被大眾埋沒。

下面對reduce的語法進行簡單說明,詳情可檢視MDN的reduce()的相關說明。

定義:對陣列中的每個元素執行一個自定義的累計器,將其結果彙總為單個返回值形式:array.reduce((t, v, i, a) => {}, initValue)引數callback:回撥函式(必選)initValue:初始值(可選)回撥函式的引數total(t):累計器完成計算的返回值(必選)value(v):當前元素(必選)index(i):當前元素的索引(可選)array(a):當前元素所屬的陣列物件(可選)執行過程以t作為累計結果的初始值,不設定t則以陣列第一個元素為初始值開始遍歷,使用累計器處理v,將v的對映結果累計到t上,結束此次迴圈,返回t進入下一次迴圈,重複上述操作,直至陣列最後一個元素結束遍歷,返回最終的t

reduce的精華所在是將累計器逐個作用於陣列成員上,把上一次輸出的值作為下一次輸入的值。下面舉個簡單的例子,看看reduce的計算結果。

const arr = [3, 5, 1, 4, 2];const a = arr.reduce((t, v) => t + v);// 等同於const b = arr.reduce((t, v) => t + v, 0);

程式碼不太明白沒關係,貼一個reduce的作用動圖應該就會明白了。

reduce實質上是一個累計器函式,透過使用者自定義的累計器對陣列的元素進行自定義累計,得出一個由累計器生成的值。另外reduce還有一個胞弟reduceRight,兩個方法的功能其實是一樣的,只不過reduce是升序執行,reduceRight是降序執行。對空陣列呼叫reduce()和reduceRight()是不會執行其回撥函式的,可認為reduce()對空陣列無效

高階用法

單憑以上一個簡單例子不足以說明reduce是個什麼。為了展示reduce的魅力,我為大家提供20種場景來應用reduce的高階用法。有部分高階用法可能需要結合其他方法來實現,這樣為reduce的多元化提供了更多的可能性。

代替map和filter
const arr = [0, 1, 2, 3];// 代替map:[0, 2, 4, 6]const a = arr.map(v => v * 2);const b = arr.reduce((t, v) => [...t, v * 2], []);// 代替filter:[2, 3]const c = arr.filter(v => v > 1);const d = arr.reduce((t, v) => v > 1 ? [...t, v] : t, []);// 代替map和filter:[4, 6]const e = arr.map(v => v * 2).filter(v => v > 2);const f = arr.reduce((t, v) => v * 2 > 2 ? [...t, v * 2] : t, []);
陣列分割
function Chunk(arr = [], size = 1) {   	return arr.length ? arr.reduce((t, v) => (t[t.length - 1].length === size ? t.push([v]) : t[t.length - 1].push(v), t), [[]]) : [];}const arr = [1, 2, 3, 4, 5];Chunk(arr, 2); // [[1, 2], [3, 4], [5]]
陣列過濾
function Difference(arr = [], oarr = []) {  return arr.reduce((t, v) => (!oarr.includes(v) && t.push(v), t), []);}const arr1 = [1, 2, 3, 4, 5];const arr2 = [2, 3, 6];Difference(arr1, arr2); // [1, 4, 5]
陣列填充
function Fill(arr = [], val = "", start = 0, end = arr.length) {  if (start < 0 || start >= end || end > arr.length) return arr;      return [            ...arr.slice(0, start),    ...arr.slice(start, end).reduce((t, v) => (t.push(val || v), t), []),    ...arr.slice(end, arr.length)  ];}const arr = [0, 1, 2, 3, 4, 5, 6];Fill(arr, "aaa", 2, 5); // [0, 1, "aaa", "aaa", "aaa", 5, 6]
陣列扁平
function Flat(arr = []) {  return arr.reduce((t, v) => t.concat(Array.isArray(v) ? Flat(v) : v), [])}const arr = [0, 1, [2, 3], [4, 5, [6, 7]], [8, [9, 10, [11, 12]]]];Flat(arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
陣列去重
function Uniq(arr = []) {  return arr.reduce((t, v) => t.includes(v) ? t : [...t, v], []);}const arr = [2, 1, 0, 3, 2, 1, 2];Uniq(arr); // [2, 1, 0, 3]
陣列最大最小值
function Max(arr = []) {  return arr.reduce((t, v) => t > v ? t : v);}function Min(arr = []) {  return arr.reduce((t, v) => t < v ? t : v);}const arr = [12, 45, 21, 65, 38, 76, 108, 43];Max(arr); // 108Min(arr); // 12
陣列成員獨立拆解
function Unzip(arr = []) {    return arr.reduce(        (t, v) => (v.forEach((w, i) => t[i].push(w)), t),        Array.from({ length: Math.max(...arr.map(v => v.length)) }).map(v => [])    );}
對陣列成員個數進行統計
function Count(arr = []) {    return arr.reduce((t, v) => (t[v] = (t[v] || 0) + 1, t), {});}const arr = [0, 1, 1, 2, 2, 2];Count(arr); // { 0: 1, 1: 2, 2: 3 }

此方法是字元統計和單詞統計的原理,入參時把字串處理成陣列即可

對陣列成員位置進行記錄
function Position(arr = [], val) {    return arr.reduce((t, v, i) => (v === val && t.push(i), t), []);}const arr = [2, 1, 5, 4, 2, 1, 6, 6, 7];Position(arr, 2); // [0, 4]
對陣列成員特性進行分組
function Group(arr = [], key) {    return key ? arr.reduce((t, v) => (!t[v[key]] && (t[v[key]] = []), t[v[key]].push(v), t), {}) : {};}const arr = [    { area: "GZ", name: "YZW", age: 27 },    { area: "GZ", name: "TYJ", age: 25 },    { area: "SZ", name: "AAA", age: 23 },    { area: "FS", name: "BBB", age: 21 },    { area: "SZ", name: "CCC", age: 19 }]; // 以地區area作為分組依據Group(arr, "area"); // { GZ: Array(2), SZ: Array(2), FS: Array(1) }
對陣列成員包含的關鍵字進行統計
function Keyword(arr = [], keys = []) {    return keys.reduce((t, v) => (arr.some(w => w.includes(v)) && t.push(v), t), []);}const text = [    "今天天氣真好,我想出去釣魚",    "我一邊看電視,一邊寫作業",    "小明喜歡同桌的小紅,又喜歡後桌的小君,真TM花心",    "最近上班喜歡摸魚的人實在太多了,程式碼不好好寫,在想入非非"];const keyword = ["偷懶", "喜歡", "睡覺", "摸魚", "真好", "一邊", "明天"];Keyword(text, keyword); // ["喜歡", "摸魚", "真好", "一邊"]
字串翻轉
function ReverseStr(str = "") {    return str.split("").reduceRight((t, v) => t + v);}const str = "reduce最牛逼";ReverseStr(str); // "逼牛最ecuder"
累加累乘
function Accumulation(...vals) {    return vals.reduce((t, v) => t + v, 0);}function Multiplication(...vals) {    return vals.reduce((t, v) => t * v, 1);}Accumulation(1, 2, 3, 4, 5); // 15Multiplication(1, 2, 3, 4, 5); // 120
非同步累計
async function AsyncTotal(arr = []) {    return arr.reduce(async(t, v) => {        const at = await t;        const todo = await Todo(v);        at[v] = todo;        return at;    }, Promise.resolve({}));}const result = await AsyncTotal(); // 需在async包圍下使用
斐波那契數列
function Fibonacci(len = 2) {    const arr = [...new Array(len).keys()];    return arr.reduce((t, v, i) => (i > 1 && t.push(t[i - 1] + t[i - 2]), t), [0, 1]);}Fibonacci(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
返回物件指定的鍵值
function GetKeys(obj = {}, keys = []) {    return Object.keys(obj).reduce((t, v) => (keys.includes(v) && (t[v] = obj[v]), t), {});}const target = { a: 1, b: 2, c: 3, d: 4 };const keyword = ["a", "d"];GetKeys(target, keyword); // { a: 1, d: 4 }
權重求和
const score = [    { score: 90, subject: "chinese", weight: 0.5 },    { score: 95, subject: "math", weight: 0.3 },    { score: 85, subject: "english", weight: 0.2 }];const result = score.reduce((t, v) => t + v.score * v.weight, 0); // 90.5
陣列轉物件
const people = [    { area: "GZ", name: "YZW", age: 27 },    { area: "SZ", name: "TYJ", age: 25 }];const map = people.reduce((t, v) => {    const { name, ...rest } = v;    t[name] = rest;    return t;}, {}); // { YZW: {…}, TYJ: {…} }
Redux Compose函式原理
function Compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {        return funs[0];    }    return funs.reduce((t, v) => (...arg) => t(v(...arg)));
相容和效能

好用是挺好用的,但是相容性如何呢?在Caniuse上搜索一番,相容性絕對的好,可大膽在任何專案上使用。不要吝嗇你的想象力,盡情發揮reduce的compose技能啦。對於時常做一些累計的功能,reduce絕對是首選方法。

另外,有些同學可能會問,reduce的效能又如何呢?下面我們透過對for-in、forEach、map和reduce四個方法同時做1~100000的累加操作,看看四個方法各自的執行時間。

// 建立一個長度為100000的陣列const list = [...new Array(100000).keys()];// for-inconsole.time("for-in");let result1 = 0;for (let i = 0; i < list.length; i++) {    result1 += i + 1;}console.log(result1);console.timeEnd("for-in");// forEachconsole.time("forEach");let result2 = 0;list.forEach(v => (result2 += v + 1));console.log(result2);console.timeEnd("forEach");// mapconsole.time("map");let result3 = 0;list.map(v => (result3 += v + 1, v));console.log(result3);console.timeEnd("map");// reduceconsole.time("reduce");const result4 = list.reduce((t, v) => t + v + 1, 0);console.log(result4);console.timeEnd("reduce");

連續做了10次以上操作,發現reduce總體的平均執行時間還是會比其他三個方法稍微快一點,所以大家還是放心使用啦!本文更多是探討reduce的使用技巧,如對reduce的相容和效能存在疑問,可自行參考相關資料進行驗證。

17
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • python 使用OpenCV進行簡單的人像分割與合成