Understand JavaScript #19 Functional Programming ft. Underscore, Loadsh
本文主要內容為探討「函式程式設計」的相關知識,透過一個經典的範例玩轉 Functional Programming,也會提到 Underscore 與 Loadsh 這兩個有名的資源庫。
一個範例瞭解函式程式設計
平常單純使用 for 迴圈的程式碼,如果想要複用邏輯,而且希望少寫一點 Code、少做重複的事情的話,可以把事情放到函式裡面去做。
1let arr1 = [1, 2, 3]; 2console.log('arr1:', arr1); 3 4// 純用 for 迴圈 5let arr2 = []; 6for (let i = 0; i < arr1.length; i++) { 7 arr2.push(arr1[i] * 2); 8} 9console.log('arr2:', arr2); 10 11// 希望之後可以少做重複的事情,所以把事情放到函式內做 12function mapForEach(arr, fn) { 13 let newArr = []; 14 for (let i = 0; i < arr.length; i++) { 15 // 用傳進去的函式 (fn) 處理傳進去的陣列 (arr) 16 newArr.push(fn(arr[i])); 17 } 18 // 最後回傳處理後的新陣列 19 return newArr; 20} 21arr2 = mapForEach(arr1, function (item) { 22 return item * 2; 23}); 24console.log('arr2:', arr2); 25 26// arr1: (3) [1, 2, 3] 27// arr2: (3) [2, 4, 6] 28// arr2: (3) [2, 4, 6] 29
這樣寫就像是讓陣列告訴我們符合條件的東西,因此我們可以重複利用 mapForEach
完成不同的任務,只要將我要的條件傳入函式即可。
如果我今天不想做數學運算,我要比較數字大小的話,我一樣可以使用 mapForEach
來完成任務。
1// 讓陣列告訴我符合條件 (function) 的東西 2let arr3 = mapForEach(arr1, function (item) { 3 return item > 2; 4}); 5console.log('arr3:', arr3); 6 7// arr3: (3) [false, false, true] 8
這個遍歷陣列的函式就是 Functional Programming 的一個經典例子,這能讓我們寫出更簡潔、直觀、易懂的程式碼。
然而限制的條件(像是大於多少的這個數字)不應該被寫死,條件應該要可以更改的,這樣才能重複使用,所以我們改良一下這個條件函式。
1// 限制條件的函式 2let checkPastLimit = function (limiter, item) { 3 return item > limiter; 4}; 5
不過 mapForEach
的 fn
只接受一個參數,我們為了要複用,不應該變動 mapForEach
,因為在出現這個需求之前,或許 mapForEach
已經使用在好幾個地方了。所以我們要想辦法讓 checkPastLimit
只需要傳一個變數 item
,而另一個變數 limiter
我們要預先設定好。
一提到預設參數,可以聯想到上一篇中的 .bind()
,它就是在複製函式的同時也預設參數。
1// 預設參數 2let arr4 = mapForEach(arr1, checkPastLimit.bind(this, 1)); 3console.log('arr4:', arr4); 4 5// arr4: (3) [false, true, true] 6
如果你想要在 mapForEach
第二個參數單純放上 checkPastLimit(limiter)
這樣子,不想在參數中出現 .bind()
,也就是 checkPastLimit
只想要傳入限制值 limiter
作為唯一的參數的話,你也可以這樣寫。
我們用 .bind()
把 presetLimiter
設定為預設的 limiter
的值(其實命名都用 limiter
也可以,這邊是為了方便區分變數),不過這個寫法與剛才其實本質上是一樣的。
1// 我的 checkPastLimit 只想要有一個參數 2let checkPastLimitSimplified = function (presetLimiter) { 3 return function (limiter, item) { 4 return item > limiter; 5 }.bind(this, presetLimiter); 6}; 7let arr5 = mapForEach(arr1, checkPastLimitSimplified(2)); 8console.log('arr5:', arr5); 9 10// arr5: (3) [false, false, true] 11
這邊其實有一個很重要的概念,就是當我們在傳入函式的時候,這些函式盡量不要變更 data,像是這邊的例子都是新陣列,沒有動到原本的陣列。
JavaScript 因為有一級函式與 Functional Programming 的概念,所以跟其他程式語言有所不同,如果我們用函式程式設計的思考方式去撰寫程式,或許才會感受到這個程式語言全部的威力。
Open Source Education: Underscore & Lodash
接下來介紹兩個有名的 JavaScript 資源庫,分別是 Underscore.js 與 Lodash.js,它們都是用來幫助處理陣列與物件集合,其中 Lodash 處理得更細節一些,可以說是對 Underscore 做一些改良之後出現的晚輩。
Underscore 使用了很多 Functional Programming 的概念,像是它在很多函式中都有傳入 iteratee
這個東西,這能讓函式執行工作,而 Lodash 則是改進了 Underscore 的一些寫法,讓執行速度更快。
使用 Underscore 的方法就如同它的名稱,下底線就是 Underscore 在全域的物件的名稱,這是一個有效的名稱喔!
1let arr1 = [1, 2, 3]; 2 3// 數字乘以 3 4let arr6 = _.map(arr1, function (item) { 5 return item * 3; 6}); 7console.log(arr6); // [3, 6, 9] 8 9// 可以被 2 整除的數字 10let arr7 = _.filter([1, 2, 3, 4, 5, 6, 7], function (item) { 11 return item % 2 === 0; 12}); 13console.log(arr7); // [2, 4, 6] 14
這些資源庫的原始碼都寫得很好,不但可以免費使用,同時藉由閱讀這些程式碼,我們可以從中學習到很好的 JavaScript 寫法,我們也稱此為開源教育。
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 一個範例玩轉函式程式設計
- 介紹 JavaScript 有名的資源庫 Underscore 與 Lodash