陣列方法可以說是 JavaScript 中最常使用的計算工具,不論是資料的處理,或是畫面的呈現,都少不了陣列方法。
在了解高階函式與複合函式的概念後,我們來試著讓陣列方法的復用性更高吧!
在前面的章節中,我們曾經使用過 map
進行資料的處理:
const list = [
{
name: '小明',
grade: 5,
},
{
name: '小可',
grade: 1,
},
{
name: '小櫻',
grade: 3,
},
];
const newList = list.map((item) => ({...item, grade: item.grade+1}));
但如果我們要針對這個陣列進行重複性的處理的話,可能會變成:
const doubleGrade = list.map((item) => ({...item, grade: item.grade * 2}));
const tripleGrade = list.map((item) => ({...item, grade: item.grade * 3}));
如果今天要處理的資料一多,多個不同的陣列同時進行類似的運算時,我們的程式碼可能就會長成:
const doubleGradeList1 = list1.map((item) => ({...item, grade: item.grade * 2}));
const tripleGradeList1 = list1.map((item) => ({...item, grade: item.grade * 3}));
const doubleGradeList2 = list2.map((item) => ({...item, grade: item.grade * 2}));
const tripleGradeList2 = list2.map((item) => ({...item, grade: item.grade * 3}));
一旦我們的程式架構複雜龐大起來,我們的軟體就會被一堆重複且不必要的程式碼,此時透過抽象化,將一些重複的細節拆解出來的話,就可以讓程式碼的復用性更高:
const doubleItemGrade = item => ({...item, grade: item.grade * 2});
const tripleItemGrade = item => ({...item, grade: item.grade * 3});
const doubleGradeList1 = list1.map(doubleItemGrade(i));
const tripleGradeList1 = list1.map(tripleItemGrade(i));
const doubleGradeList2 = list2.map(doubleItemGrade(i));
const tripleGradeList2 = list2.map(tripleItemGrade(i));
在上方的範例中,我們應用了高階函式的技巧,在 map 中分別傳入 doubleItemGrade
與 tripleItemGrade
函式,未來若是要處理類似資料結構的屬性計算,就不需要一一在每個 map 函式中撰寫一次新的函式。
當然,如果我們認真觀察後,可能還會發現一些重複的細節,例如:被不同手段處理的陣列本人可以被當成固定參數,或是我們可以把處理 grade
屬性的方法的倍數由外層決定!於是我們就可以再次利用科里化的方式,嘗試固定住陣列的資料,所以程式碼就會變成:
const multipleGrade = times => item => ({...item, grade: item.grade * times});
const curriedMap = ary => func => ary.map(func);
const dealWithList = curriedMap(list);
const doubleGrade = dealWithList(multipleGrade(2));
const tripleGrade = dealWithList(multipleGrade(3));
...
登愣!當程式碼要處理的邏輯一多時,我們就透過抽象化思考,將重複的邏輯封裝成一個函式,這些函式的細節全由參數做決定,同時夠過複合函式與高階函式的重複組合,讓大部分的程式碼可以重複再利用。
當然,除了 map
以外,JavaScript 中的陣列方法,都可以透過柯里化的技巧,讓程式碼看起來一致也更「FP」:
const curriedMap = ary => func => ary.map(func);
const curriedFilter = ary => func => ary.filter(func);
const curriedRuduce = init => funcs => funcs.reduce(((x, func) => func(x) ), init);
看到這裡,也許你會跟我當初初識 FP 時一樣驚訝,除了 map
與 filter
外,連 reduce
都可以透過複合函式與高階函式的手段進行科理化。
透過這個技巧優化完的函式,完完全全符合我們先前在聊到抽象化時,所提到的「廣義化」與「特殊化」,這些函式不僅可以符合大部分計算的需求,也可以透過柯里化固定參數的手法,讓函式可以針對一些特定的計算,讓資料再次重複使用。
而比起 map
與 filter
,我認為 reduce
能處理的任務又更複雜了一點, 如果這邊覺得 curriedRuduce
有點複雜也沒關係,在下一章節中,我們會針對 curriedRuduce
進行拆解,並透過柯里化 reduce
來實作能讓前端函式自動化的 pipe !那我們就下一章節見啦。