在準備 Functional Programming 讀書會進度時,讀到 first-class function 的特性,發現可以把 function 當作參數傳遞,超有彈性!為了深入理解,這次刻意不使用 Array 內建方法,改用自訂 function 來實作以下功能:
- forEach
- map
- filter
- reduce
透過這種 function 的方式,不僅能隱藏資料結構與實作細節,還能讓程式碼更乾淨,保持運算邏輯不變,並提供固定的 interface 給外部使用,超整齊!
另外,還玩了一下 jsbenchmark,用來測試 JavaScript 在瀏覽器中的執行效能,順便把測試結果加進來。以下效能測試的假資料 DATA 是用 1 到 1000 的陣列。
4 種 Function 實作
forEach
const forEach = <T>(array: T[], f: (item: T) => void): void => {
for (let index = 0; index < array.length; index++) {
const item = array[index];
f(item);
}
}
forEach 能把資料陣列和要執行的 function 傳進去,逐一處理每個項目。順便比較了 for...in 和 for...of 的效能:
- for...in:因為要檢查物件(或陣列)的所有可列舉屬性,包括自訂屬性和原型鏈上的屬性,每次迭代得多做屬性檢查和型別轉換,效能比較差。
- for...of:直接迭代陣列元素,簡單明瞭,跑得比較快。
map
const map = <T, U>(array: T[], f: (item: T) => U): U[] => {
const newArray: U[] = [];
forEach(array, (element) => newArray.push(f(element)));
return newArray;
};
map 就是在 forEach 的基礎上,對每個元素做處理,透過傳入的 function 轉換資料,保持陣列順序不變,回傳新陣列。
filter
const filter = <T>(array: T[], f: (item: T) => boolean): T[] => {
const newArray: T[] = [];
forEach(array, (element) => {
if (f(element)) newArray.push(element);
});
return newArray;
};
filter 跟 map 的差別在於,傳入的 function 是用來做條件判斷,只留下符合條件的元素。
reduce
const reduce = <T, U>(array: T[], init: U, f: (acc: U, element: T) => U): U => {
let acc = init;
forEach(array, (element) => {
acc = f(acc, element);
});
return acc;
};
reduce 超常用來做資料累加,這邊以加總為例,需傳入:
- array:資料陣列
- init:初始值
- f:處理邏輯的 function
小結
forEach 是核心基礎,其他 function 都建構在它之上。如果遇到效能瓶頸,只要改進 forEach,就能全面提升效能。如果四個 function 只能留一個,你會選哪個?😎
用 reduce 實作 map
如果只能用 reduce,來看看怎麼實作 map,有兩種方式:
push
const map = <T, U>(array: T[], f: (item: T) => U): U[] => {
return reduce(array, [] as U[], (ret, item) => {
ret.push(f(item));
return ret;
});
};
簡單來說,把初始值設為空陣列
,每次用 push 把處理後的元素加進去。
[]
concat
const map = <T, U>(array: T[], f: (item: T) => U): U[] => {
return reduce(array, [] as U[], (ret, item) => {
return ret.concat(f(item));
});
};
用 concat 也能做到,但效能比 push 差不少,因為每次都要產生新陣列。
用 reduce 實作 filter
跟原本的 filter 邏輯差不多,透過傳入的 function 做條件判斷,符合條件的元素用 push 或 concat 加到初始陣列。
push
const filter = <T>(array: T[], f: (element: T) => boolean): T[] => {
return reduce(array, [] as T[], (ret, item) => {
if (f(item)) ret.push(item);
return ret;
});
};
concat
const filter = <T>(array: T[], f: (element: T) => boolean): T[] => {
return reduce(array, [] as T[], (ret, item) => {
if (f(item)) return ret.concat(item);
return ret;
});
};
總結
以前用 array method都很順手,沒想過如果自己寫,可以怎麼寫,覺得如果面試考這種,其實可以考出基本功與熟悉度,特別是用 reduce來實作其他兩種 filter與 map,前面提到 functional programming的書,是 簡約的軟體開發思維: 用Functional Programming重構程式, 以JavaScript為例,與其在框架打滾,不如看看可以增加思考方式的書籍。
文字工作者,寫作時間常常在人類與電腦之間拉鋸,相信閱讀,相信文字與思想所構築的美麗境界