Array.prototype.reduce 實用指南

sea_ljf發表於2018-10-06

hello~親愛的看官老爺們大家好~最近因為工(lan)作(ai)繁(fa)忙(zuo),出產的文章多以譯文為主,之前翻譯了《如何在 JavaScript 中更好地使用陣列》一文,發現不少同學對 Array.prototype.reduce 不太熟悉,而我正好在這方面有一點積累,在此分享給大家。

Array.prototype.reduce 算是 JavaScript 陣列中比較難用但又特別強大的方法,本文以實用為主,通過例子展示如何使用這個方法,但並不深挖這個方法的本質(深入的話涉及到很多函數語言程式設計相關的知識)~以下是正文。

Array.prototype.reduce 的簡單介紹

reduce() 方法對累加器和陣列中的每個元素(從左到右)應用一個函式,將其簡化為單個值。

上述是 MDN對該方法的描述,方法的語法是: arr.reduce(callback[, initialValue])callback 接受四個引數,分別是:accumulator,累加器累加回撥的返回值; currentValue,陣列中正在處理的元素;currentIndex(可選),陣列中正在處理的當前元素的索引;array(可選),呼叫 reduce() 的陣列。initialValue 為可選引數,作為第一次呼叫 callback 函式時的第一個引數的值。方法的返回值是函式累計處理的結果。

一股腦介紹完之後,估計不少同學都是比較懵的。其實這個方法並不難理解的,正如它名字所示,抓住它的核心:聚合。一般而言,如果需要把陣列轉換成其他元素,如字串、數字、物件甚至是一個新陣列的時候,若其他陣列方法不太適用時,就可以考慮 reduce 方法,不熟悉這個方法的同學,儘管拋開上面的語法, 記住方法的核心是聚合即可。

下文的例子都用到以下陣列,假設通過介面獲取到如下的資料體:

[{
  id: 1,
  type: 'A',
  total: 3
}, {
  id: 2,
  type: 'B',
  total: 5
}, {
  id: 3,
  type: 'E',
  total: 7
},...]
複製程式碼

資料體是按照 id 的升序進行排列,totaltype 不定~

聚合為數字

根據上述資料體,我們一起來做第一個小需求,統計 total 的總和。如果不用 reduce,其實也不難:

function sum(arr) {
  let sum = 0;
  for (let i = 0, len = arr.length; i < len; i++) {
    const { total } = arr[i];
    sum += total;
  }
  return sum;
}
複製程式碼

這個函式可以完成上述需求,但我們精確地維護了陣列索引,再精確地處理整個運算過程,是典型的指令式程式設計。上文提及,只要涉及將陣列轉換為另外的資料體,就可以使用 reduce,它可以這樣寫:

arr.reduce((sum, { total }) => {
  return sum + total;
}, 0)
複製程式碼

這樣就完成了~sum 是此前累加的結果,它的初始值為 0。每次將此前的累計值加上當前項的 total 為此次回撥函式的返回值,作為下次執行時 sum 的實參使用。看起來比較繞,可以參考下面的表格:

輪次 sum total 返回值
1 0(初始值) 3 3
2 3 5 8
3 8 7 15
... ... ... ...

如此是不是清晰了很多?前一次的返回值就是後一次 sum 的值,如此類推,最後累積出總和,將陣列聚合成了數字。

聚合為字串

下一個需求是將陣列的每項轉換為固定格式的字串(如第一項轉換為 id:1,type:A;),每項直接以分號作為分隔。一般來說,陣列轉為字串,join 方法是不錯的選擇,但並不適用於需要精確控制或陣列的項比較複雜的情況。在本例中,join 方法是達不到我們想要的效果的。

使用 for 迴圈當然可以解決問題,但 reduce 也許是更好的選擇,程式碼如下:

arr.reduce((str, { id, type }) => {
  return str + `id:${id},type:${type};`;
}, '')
複製程式碼

有了聚合為數字的例子,這次你能在腦海中模擬出執行的過程麼?以下也是前三項的執行過程:

輪次 str id type 返回值
1 ''(初始值) 1 'A' 'id:1,type:A;'
2 'id:1,type:A;' 2 'B' 'id:1,type:A;id:2,type:B;'
3 'id:1,type:A;id:2,type:B;' 3 'E' 'id:1,type:A;id:2,type:B;id:3,type:E;'
... ... ... ... ...

聚合為物件

有了前面的一點基礎,可以做複雜一點的聚合了。上面的資料體是比較典型的後端介面返回結果,但對於前端來說,轉換成 key value 的物件形式,更利於進行之後的操作。那我們就以轉換為 keyidvalue 是其他屬性的物件作為目標吧!

function changeToObj(arr) {
  const res = {};
  arr.forEach(({ id, type, total }) => {
    res[id] = {
      type,
      total
    };
  })
  return res;
}
複製程式碼

如上所示,這個函式可以很好地完成我們的目標。但略顯囉嗦,記住:只要目標是將陣列聚合為唯一的元素時,都可以考慮使用 reduce。這個例子恰好符合:

arr.reduce((res, { id, type, total }) => {
  res[id] = {
    type,
    total
  };
  return res;
}, {})
複製程式碼

res 是最後返回的物件,通過遍歷陣列,不斷往裡面新增新的屬性與值,最後達到聚合成物件的目的,程式碼還是相當簡潔有力的。

最後,對於不熟悉這個方法的同學,不妨練習一下,將資料體轉換為一個字串陣列,陣列每一項為原陣列 type 的值。

小結

以上就是本文的全部內容,這裡稍微小結一下 reduce 的優缺點~原則上說,只要是將陣列聚合為唯一的元素時,都可以實現它。同時,它在函數語言程式設計中有一席之地,也是宣告式程式設計的典型例子。這也意味著它不容易掌握,如果熟悉 reduce 方法,寫出來的程式碼可讀性強,十分優雅。但在不熟悉的同學眼裡,這就是不折不扣的天書了。如何更好地使用 reduce,避免寫出難以維護的程式碼,值得每一位同學思考。

感謝各位看官大人看到這裡,知易行難,希望本文對你有所幫助~謝謝!

相關文章