JS函數(shù)式編程之純函數(shù)、柯里化以及組合函數(shù)
前言
函數(shù)式編程(Functional Programming),又稱為泛函編程,是一種編程范式。
早在很久以前就提出了函數(shù)式編程這個概念了,而后面一直長期被面向?qū)ο缶幊趟y(tǒng)治著,最近幾年函數(shù)式編程又回到了大家的視野中,JavaScript是一門以函數(shù)為第一公民的語言,必定是支持這一種編程范式的。
下面就來談?wù)凧avaScript函數(shù)式編程中的核心概念純函數(shù)、柯里化以及組合函數(shù)。
純函數(shù)
純函數(shù)的概念
對于純函數(shù)的定義,維基百科中是這樣描述的:在程序設(shè)計中,若函數(shù)符合以下條件,那么這個函數(shù)被稱之為純函數(shù)。
- 此函數(shù)在相同的輸入值時,需產(chǎn)生相同的輸出;
- 函數(shù)的輸入和輸出值以外的其他隱藏信息或狀態(tài)無關(guān),也和由I/O設(shè)備產(chǎn)生的外部輸出無關(guān);
- 該函數(shù)不能有語義上可觀察的函數(shù)副作用,諸如“觸發(fā)事件”,使輸出設(shè)備輸出,或更改輸出值以外物件的內(nèi)容等;
對以上描述總結(jié)就是:
- 對于相同的輸入,永遠會得到相同的輸出;
- 在函數(shù)的執(zhí)行過程中,沒有任何可觀察的副作用;
- 同時也不依賴外部環(huán)境的狀態(tài);
副作用
上面提到了一個詞叫“副作用”,那么什么是副作用呢?
- 通常我們所說的副作用大多數(shù)是指藥會產(chǎn)生的副作用;
- 而在計算機科學(xué)中,副作用指在執(zhí)行一個函數(shù)時,除了得到函數(shù)的返回值以外,還在函數(shù)調(diào)用時產(chǎn)生了附加的影響,比如修改了全局變量的狀態(tài),修改了傳入的參數(shù)或得到了其它的輸出內(nèi)容等;
純函數(shù)案例
- 編寫一個求和的函數(shù)sum,只要我們輸入了固定的值,sum函數(shù)就會給我們返回固定的結(jié)果,且不會產(chǎn)生任何副作用。
function sum(a, b) { return a + b } const res = sum(10, 20) console.log(res) // 30
- 以下的sum函數(shù)雖然對于固定的輸入也會返回固定的輸出,但是函數(shù)內(nèi)部修改了全局變量message,就認(rèn)定為產(chǎn)生了副作用,不屬于純函數(shù)。
let message = 'hello' function sum(a, b) { message = 'hi' return a + b }
- 在JavaScript中也提供了許多的內(nèi)置方法,有些是純函數(shù),有些則不是。像操作數(shù)組的兩個方法slice和splice。
1.slice方法就是一個純函數(shù),因為對于同一個數(shù)組固定的輸入可以得到固定的輸出,且沒有任何副作用;
const nums = [1, 2, 3, 4, 5] const newNums = nums.slice(1, 3) console.log(newNums) // [2, 3] console.log(nums) // [ 1, 2, 3, 4, 5 ]
2.splice方法不是一個純函數(shù),因為它改變了原數(shù)組nums;
const nums = [1, 2, 3, 4, 5] const newNums = nums.splice(1, 3) console.log(newNums) // [ 2, 3, 4 ] console.log(nums) // [ 1, 5 ]
柯里化
柯里化的概念
對于柯里化的定義,維基百科中是這樣解釋的:
- 柯里化是指把接收多個參數(shù)的函數(shù),變成接收一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接收余下的參數(shù),而且返回結(jié)果的新函數(shù)的技術(shù);
- 柯里化聲稱**“如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個函數(shù)”**;
總結(jié):只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩余的參數(shù)的過程就稱之為柯里化。
函數(shù)柯里化的過程
編寫一個普通的三值求和函數(shù):
function sum(x, y, z) { return x + y + z } const res = sum(10, 20, 30) console.log(res) // 60
將以上求和函數(shù)柯里化得:
- 將傳入的三個參數(shù)進行拆解,依次返回一個函數(shù),并傳入一個參數(shù);
- 在保證同樣功能的同時,其調(diào)用方式卻發(fā)生了變化;
- 注意:在拆解參數(shù)時,不一定非要將參數(shù)拆成一個個的,也可以拆成2+1或1+2;
function sum(x) { return function(y) { return function(z) { return x + y + z } } } const res = sum(10)(20)(30) console.log(res)
使用ES6箭頭函數(shù)簡寫為:
const sum = x => y => z => x + y + z
函數(shù)柯里化的特點及應(yīng)用
- **讓函數(shù)的職責(zé)更加單一。**柯里化可以實現(xiàn)讓一個函數(shù)處理的問題盡可能的單一,而不是將一大堆邏輯交給一個函數(shù)來處理。
1.將上面的三值求和函數(shù)增加一個需求,在計算結(jié)果之前給每個值加上2,先看看不使用柯里化的實現(xiàn)效果:
function sum(x, y, z) { x = x + 2 y = y + 2 z = z + 2 return x + y + z }
2.柯里化的實現(xiàn)效果:
function sum(x) { x = x + 2 return function(y) { y = y + 2 return function(z) { z = z + 2 return x + y + z } } }
3.很明顯函數(shù)柯里化后,讓我們對每個參數(shù)的處理更加單一
- **提高函數(shù)參數(shù)邏輯復(fù)用。**同樣使用上面的求和函數(shù),增加另一個需求,固定第一個參數(shù)的值為10,直接看柯里化的實現(xiàn)效果吧,后續(xù)函數(shù)調(diào)用時第一個參數(shù)值都為10的話,就可以直接調(diào)用sum10函數(shù)了。
function sum(x) { return function(y) { return function(z) { return x + y + z } } } const sum10 = sum(10) // 指定第一個參數(shù)值為10的函數(shù) const res = sum10(20)(30) console.log(res) // 60
自動柯里化函數(shù)的實現(xiàn)
function autoCurrying(fn) { // 1.拿到當(dāng)前需要柯里化函數(shù)的參數(shù)個數(shù) const fnLen = fn.length // 2.定義一個柯里化之后的函數(shù) function curried\_1(...args1) { // 2.1拿到當(dāng)前傳入?yún)?shù)的個數(shù) const argsLen = args1.length // 2.1.將當(dāng)前傳入?yún)?shù)個數(shù)和fn需要的參數(shù)個數(shù)進行比較 if (argsLen >= fnLen) { // 如果當(dāng)前傳入的參數(shù)個數(shù)已經(jīng)大于等于fn需要的參數(shù)個數(shù) // 直接執(zhí)行fn,并在執(zhí)行時綁定this,并將對應(yīng)的參數(shù)數(shù)組傳入 return fn.apply(this, args1) } else { // 如果傳入的參數(shù)不夠,說明需要繼續(xù)返回函數(shù)來接收參數(shù) function curried\_2(...args2) { // 將參數(shù)進行合并,遞歸調(diào)用curried\_1,直到參數(shù)達到fn需要的參數(shù)個數(shù) return curried\_1.apply(this, [...args1, ...args2]) } // 返回繼續(xù)接收參數(shù)函數(shù) return curried\_2 } } // 3.將柯里化的函數(shù)返回 return curried\_1 }
測試:
function sum(x, y, z) { return x + y + z } const curryingSum = autoCurrying(sum) const res1 = curryingSum(10)(20)(30) const res2 = curryingSum(10, 20)(30) const res3 = curryingSum(10)(20, 30) const res4 = curryingSum(10, 20, 30) console.log(res1) // 60 console.log(res2) // 60 console.log(res3) // 60 console.log(res4) // 60
組合函數(shù)
**組合函數(shù)(Compose Function)**是在JavaScript開發(fā)過程中一種對函數(shù)的使用技巧、模式。對某一個數(shù)據(jù)進行函數(shù)調(diào)用,執(zhí)行兩個函數(shù),這兩個函數(shù)需要依次執(zhí)行,所以需要將這兩個函數(shù)組合起來,自動依次調(diào)用,而這個過程就叫做函數(shù)的組合,組合形成的函數(shù)就叫做組合函數(shù)。
需求:對一個數(shù)字先進行乘法運算,再進行平方運算。
- 一般情況下,需要先定義兩個函數(shù),然后再對其依次調(diào)用:
function double(num) { return num * 2 } function square(num) { return num ** 2 } const duobleResult = double(10) const squareResult = square(duobleResult) console.log(squareResult) // 400
實現(xiàn)一個組合函數(shù),將duoble和square兩個函數(shù)組合起來:
function composeFn(fn1, fn2) { return function(num) { return fn2(fn1(num)) } } const execFn = composeFn(double, square) const res = execFn(10) console.log(res) // 400
實現(xiàn)一個自動組合函數(shù)的函數(shù):
function autoComposeFn(...fns) { // 1.拿到需要組合的函數(shù)個數(shù) const fnsLen = fns.length // 2.對傳入的函數(shù)進行邊界判斷,所有參數(shù)必須為函數(shù) for (let i = 0; i < fnsLen; i++) { if (typeof fns[i] !== 'function') { throw TypeError('The argument passed must be a function.') } } // 3.定義一個組合之后的函數(shù) function composeFn(...args) { // 3.1.拿到第一個函數(shù)的返回值 let result = fns[0].apply(this, args) // 3.1.判斷傳入的函數(shù)個數(shù) if (fnsLen === 1) { // 如果傳入的函數(shù)個數(shù)為一個,直接將結(jié)果返回 return result } else { // 如果傳入的函數(shù)個數(shù) >= 2 // 依次將函數(shù)取出進行調(diào)用,將上一個函數(shù)的返回值作為參數(shù)傳給下一個函數(shù) // 從第二個函數(shù)開始遍歷 for (let i = 1; i < fnsLen; i++) { result = fns[i].call(this, result) } // 將結(jié)果返回 return result } } // 4.將組合之后的函數(shù)返回 return composeFn }
測試:
function double(num) { return num * 2 } function square(num) { return num ** 2 } const composeFn = autoComposeFn(double, square) const res = composeFn(10) console.log(res) // 400
到此這篇關(guān)于JS函數(shù)式編程之純函數(shù)、柯里化以及組合函數(shù)的文章就介紹到這了,更多相關(guān)純函數(shù)、柯里化、組合函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js監(jiān)控IE火狐瀏覽器關(guān)閉、刷新、回退、前進事件
本節(jié)主要介紹了js監(jiān)控IE火狐瀏覽器關(guān)閉、刷新、回退、前進事件的方法2014-07-07JS 動態(tài)加載js文件和css文件 同步/異步的兩種簡單方式
下面小編就為大家?guī)硪黄狫S 動態(tài)加載js文件和css文件 同步/異步的兩種簡單方式。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09