初見函數式編程
在學習 JS 的過程中時常會聽到一個名次——“函數式編程”,那麼究竟什麼是函數式編程,函數式編程又有什麼優點,這就在這篇博客進行一個簡單的總結吧~
主要內容:
- 函數式編程的概念
- 函數式編程的優點與示例
什麼是函數式編程
首先,我們放下編程的概念,我們來看函數。
函數的概念來自於數學,數學中的函數 f(x) = y
有一個非常重要的特點對於一個給定的 x,有唯一的 y 與其對應(這就是爲什麼橢圓曲線不是函數)
然而在編程中,函數並不具有這個特點,舉個栗子:
let val = 1
function add(x){
return x + val
}
console.log(add(1)) // 2
val += 1
console.log(add(1)) // 3
可以看到編程中的函數在參數相同的情況下,允許有不同的返回值——只要依賴於函數外部的量
那麼當函數依賴於外部的變量(常量不會有這樣的問題),並且函數在多處調用,就有可能出現 bug,函數的調用結果可能會和預期結果相去甚遠
而函數式編程就要求我們規避這樣的情況,讓所有函數對於相同的輸入的返回值相同,這樣的特性就叫做引用透明性,這就是函數式編程的核心特性!
一個符合引用透明性的函數的栗子:
function id(x){ return x; }
函數式編程帶來的優點
在上文中提到過,編程語言中的函數大多是不滿足數學中的函數的概念的,so 我們將滿足數學函數條件的函數稱爲“純函數”
函數式編程的優點大多都來自於純函數
可測試性
除了測試人員進行的全方位測試外,我們在開發過程中往往要對自己寫的代碼進行模塊測試
在開發中,我受非純函數迫害已久,由於它依賴了外部變量(比如存儲在 localStorage 中的數據)我不得不三番五次去檢查這些外部變量是否在某個過程中被改變甚至是刪除
let val = 1
function add(x){
return x + val
}
console.log(add(1)) // 2
// 在未知因素影響下 val被改變
console.log(add(1)) // 預期結果 2,實際輸出 emmmmm
如果我沒有注意外部依賴而是一頭扎進函數邏輯裏,可能永遠都找不到這個bug
代碼的併發性
雖然我們都知道 JS 是一門單線程語言(關於JS的執行可以參見->技術總結——JS的執行順序),但是我們爲了提高前端的性能可能會通過 WebWorker
來併發執行多個任務,或者在 Node 環境下 JS 併發執行函數
這個時候就是對非純函數的一個很大的考驗:
let global = "全局變量"
let func1 = ()=>{
global = "全局變量被改變了"
// 一些邏輯
}
let func2 = ()=>{
if(global = "全局變量"){
return true
}
}
上面的兩個函數都依賴於外部的global,當它們併發執行,func1就會對func2產生影響,如果將它們變爲純函數就不會有這樣的問題:
let global = "全局變量"
let func1 = (x)=>{
x = "全局變量被改變了"
// 一些邏輯
}
let func2 = (x)=>{
if(x = "全局變量"){
return true
}
}
函數執行的緩存
當我們的函數都是純函數,而我們又會多次調用函數,我們就可以對函數對象進行一個緩存
比如我們需要大量計算數字的4次方,我們可以建立一個映射表用來緩存函數的執行結果:
// 映射表
let fourTimesTable = {};
let fourTimes = (x){
return x*x*x*x
}
// 檢查表中是否有 2 的四次方,如果有就返回,如果沒有就執行函數避免運算
fourTimesTable.hasOwnProperty(2)?
fourTimesTable[2]:
fourTimesTable[2] = fourTimes(2)
管道與組合
管道過濾器是一種很經典的設計模式,我們可以將這種模式和函數式編程結合起來
在管道和過濾器軟件體系結構中,每個模塊都有一組輸入和一組輸出。每個模塊從它的輸入端接收輸入數據流,在其內部經過處理後,按照標準的順序,將結果數據流送到輸出端,以達到傳遞一組完整的計算結果實例的目的。
在這種結構中,各模塊之間的連接器充當了數據流的導管,將一個過濾器的輸出傳到下一個過濾器的輸入端。所以,這種連接器稱爲“管道”。
我們也可以將這樣的私用運用在函數式編程中,將一個個函數作爲過濾器,通過函數的組合,形成一條數據處理的通路:
// 參數累加
function add(...args){
let result = args.reduce((prev, cur, index, arr)=> {
return prev + cur;
})
return result
}
// 參數累乘
function times(...args){
let result = args.reduce((prev, cur, index, arr)=> {
return prev * cur;
})
return result
}
let arr1=[1,3,6],arr2=[2,5,21],arr3=[3,7,8,27,4]
// 三組數據,要求組內累乘,然後結果累加
add(times(...arr1),times(...arr2),times(...arr3))
// 三組數據,要求組內累加,然後結果累乘
times(add(...arr1),add(...arr2),add(...arr3))
對函數式編程的初探暫止於此,進一步學習後再做總結~