Functional Programming 隱藏數據結構的方法與效能

Jul 11, 2025

在準備 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...infor...of 的效能:

  • for...in:因為要檢查物件(或陣列)的所有可列舉屬性,包括自訂屬性和原型鏈上的屬性,每次迭代得多做屬性檢查和型別轉換,效能比較差。
  • for...of:直接迭代陣列元素,簡單明瞭,跑得比較快。

forEach Benchmark-half.webp

效能測試

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 轉換資料,保持陣列順序不變,回傳新陣列。

Map Benchmark-half.webp

效能測試

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; };

filtermap 的差別在於,傳入的 function 是用來做條件判斷,只留下符合條件的元素。

Filter from JS Benchmark-half.webp

效能測試

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

Reduce Benchmark-half.webp

效能測試

小結

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 差不少,因為每次都要產生新陣列。

Map Use Reduce-half.webp

效能測試

用 reduce 實作 filter

跟原本的 filter 邏輯差不多,透過傳入的 function 做條件判斷,符合條件的元素用 pushconcat 加到初始陣列。

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; }); };

Filter Use Reduce-half.webp

效能測試

總結

以前用 array method都很順手,沒想過如果自己寫,可以怎麼寫,覺得如果面試考這種,其實可以考出基本功與熟悉度,特別是用 reduce來實作其他兩種 filtermap,前面提到 functional programming的書,是 簡約的軟體開發思維: 用Functional Programming重構程式, 以JavaScript為例,與其在框架打滾,不如看看可以增加思考方式的書籍。

Ekman Hsieh

文字工作者,寫作時間常常在人類與電腦之間拉鋸,相信閱讀,相信文字與思想所構築的美麗境界