函數式程式設計

假設我們要將下面一組數字進行排序:

var input = [3, 2, 4, 1]

很快地,我們可能就想出以下的程式碼來達成需求:

function sortInput() {
// 去掉奇數
for (let i = 0; i < input.length; ++i) {
for (let j = 0; j < input.length - i - 1; ++j) {
if (input[j] > input[j + 1]) {
[input[j], input[j + 1]] = [input[j + 1], input[j]]
}
}
}
}
var input = [3, 2, 4, 1]
sortInput()
console.log(input) // [1, 2, 3, 4]

很快地,你可能會發現這個方法沒辦法重複使用,因為你沒辦法排序其他數列:

function sort(input) {
// 去掉奇數
for (let i = 0; i < input.length; ++i) {
for (let j = 0; j < input.length - i - 1; ++j) {
if (input[j] > input[j + 1]) {
[input[j], input[j + 1]] = [input[j + 1], input[j]]
}
}
}
return input
}
var input = [3, 2, 4, 1]
console.log(sort(input)) // [1, 2, 3, 4]
console.log(sort([5, 8, 7, 6])) // [5, 6, 7, 8]

假設,此時有一個排序,它是要降冪排序:

function sortBy(comparator, input) {
// 去掉奇數
for (let i = 0; i < input.length; ++i) {
for (let j = 0; j < input.length - i - 1; ++j) {
if (input[j] > input[j + 1]) {
[input[j], input[j + 1]] = [input[j + 1], input[j]]
}
}
}
return input
}

此時,得到一個意外的答案,奇數的陣列竟然是空的:

var input = [1, 2, 3, 4]
console.log(getEvenNumbers2(input)) // [2, 4]
console.log(getOddNumbers(input)) // []

原因是 Array.prototype.splice這類的函式帶有「副作用」,它會更改原始陣列,讓我們優化一下:

function getEvenNumbers3(input) {
// 先進行淺拷貝
input = input.slice()
for (let i = input.length - 1; i >= 0; --i)
if (input[i] % 2 === 1)
input.splice(i, 1)
return input
}
function getOddNumbers2(input) {
// 先進行淺拷貝
input = input.slice()
for (let i = input.length - 1; i >= 0; --i)
if (input[i] % 2 === 0)
input.splice(i, 1)
return input
}

接著測試看看:

var input = [1, 2, 3, 4]
console.log(getEvenNumbers3(input)) // [2, 4]
console.log(getOddNumbers2(input)) // [1, 3]

一切正常,但你可能會問 getEvenNumbers3 好像與 getOddNumbers2 看起來差不多?

function filter(predicate, input) {
// 先進行淺拷貝
input = input.slice()
for (let i = input.length - 1; i >= 0; --i)
if (predicate(input[i]) === false)
input.splice(i, 1)
return input
}

抽取共有的邏輯之後,我們可以得到一個 filter 「高階函式」,接受一個斷言函式,用以判斷是否要保留各個元素:

var input = [1, 2, 3, 4]
const isOdd = n => n % 2 === 1
const isEven = n => n % 2 === 0
console.log(filter(isOdd, input)) // [1, 3]
console.log(filter(isEven, input)) // [2, 4]

我們可以透過日常將這些函式抽取出來,來提升開發速度,也可以直接使用社群維護的函式庫,例如「Ramda」:

import * as R from 'ramda'
var input = [1, 2, 3, 4]
const isOdd = n => n % 2 === 1
const isEven = n => n % 2 === 0
console.log(R.filter(isOdd, input)) // [1, 3]
console.log(R.filter(isEven, input)) // [2, 4]

結論

  • 提升開發速度 🚀
  • 代碼可讀性高
  • 降低專案體積 📦
  • 避免邊夜問題
  • 容易除錯追蹤
  • 提升可測試性
  • 族繁不及備載
函數式程式設計 假設我們要將下面一組數字進行排序: var input = [ 3 , 2 , 4 , 1 ] 很快地,我們可能就想出以下的程式碼來達成需求: function sortInput ( ) { // 去掉奇數 for ( let i = 0 ; i < input. length ; ++i) { for ( let j = 0 ; j < input. length - i - 1 ; ++j) { if (input[j] > input[j + 1 ]) { [input[j], input[j + 1 ]] = [input[j + 1 ], input[j]] } } } } var input = [ 3 , 2 , 4 , 1 ] sortInput () console . log (input) // [1, 2, 3, 4] 很快地,你可能會發現這個方法沒辦法重複使用,因為你沒辦法排序其他數列: function sort ( input ) { // 去掉奇數 for ( let i = 0 ; i < input. length ; ++i) { for ( let j = 0 ; j < input. length - i - 1 ; ++j) { if (input[j] > input[j + 1 ]) { [input[j], input[j + 1 ]] = [input[j + 1 ], input[j]] } } } return input } var input = [ 3 , 2 , 4 , 1 ] console . log ( sort (input)) // [1, 2, 3, 4] console . log ( sort ([ 5 , 8 , 7 , 6 ])) // [5, 6, 7, 8] 假設,此時有一個排序,它是要降冪排序: function sortBy ( comparator, input ) { // 去掉奇數 for ( let i = 0 ; i < input. length ; ++i) { for ( let j = 0 ; j < input. length - i - 1 ; ++j) { if (input[j] > input[j + 1 ]) { [input[j], input[j + 1 ]] = [input[j + 1 ], input[j]] } } } return input } 此時,得到一個意外的答案,奇數的陣列竟然是空的: var input = [ 1 , 2 , 3 , 4 ] console . log ( getEvenNumbers2 (input)) // [2, 4] console . log ( getOddNumbers (input)) // [] 原因是 Array.prototype.splice 這類的函式帶有「副作用」,它會更改原始陣列,讓我們優化一下: function getEvenNumbers3 ( input ) { // 先進行淺拷貝 input = input. slice () for ( let i = input. length - 1 ; i >= 0 ; --i) if (input[i] % 2 === 1 ) input. splice (i, 1 ) return input } function getOddNumbers2 ( input ) { // 先進行淺拷貝 input = input. slice () for ( let i = input. length - 1 ; i >= 0 ; --i) if (input[i] % 2 === 0 ) input. splice (i, 1 ) return input } 接著測試看看: var input = [ 1 , 2 , 3 , 4 ] console . log ( getEvenNumbers3 (input)) // [2, 4] console . log ( getOddNumbers2 (input)) // [1, 3] 一切正常,但你可能會問 getEvenNumbers3 好像與 getOddNumbers2 看起來差不多? function filter ( predicate, input ) { // 先進行淺拷貝 input = input. slice () for ( let i = input. length - 1 ; i >= 0 ; --i) if ( predicate (input[i]) === false ) input. splice (i, 1 ) return input } 抽取共有的邏輯之後,我們可以得到一個 filter 「高階函式」,接受一個斷言函式,用以判斷是否要保留各個元素: var input = [ 1 , 2 , 3 , 4 ] const isOdd = n => n % 2 === 1 const isEven = n => n % 2 === 0 console . log ( filter (isOdd, input)) // [1, 3] console . log ( filter (isEven, input)) // [2, 4] 我們可以透過日常將這些函式抽取出來,來提升開發速度,也可以直接使用社群維護的函式庫,例如「Ramda」: import * as R from 'ramda' var input = [ 1 , 2 , 3 , 4 ] const isOdd = n => n % 2 === 1 const isEven = n => n % 2 === 0 console . log (R. filter (isOdd, input)) // [1, 3] console . log (R. filter (isEven, input)) // [2, 4] 結論 提升開發速度 🚀 代碼可讀性高 降低專案體積 📦 避免邊夜問題 容易除錯追蹤 提升可測試性 … 族繁不及備載