JS函數(shù)式編程之純函數(shù)、柯里化以及組合函數(shù)
前言
函數(shù)式編程(Functional Programming),又稱為泛函編程,是一種編程范式。
早在很久以前就提出了函數(shù)式編程這個(gè)概念了,而后面一直長期被面向?qū)ο缶幊趟y(tǒng)治著,最近幾年函數(shù)式編程又回到了大家的視野中,JavaScript是一門以函數(shù)為第一公民的語言,必定是支持這一種編程范式的。
下面就來談?wù)凧avaScript函數(shù)式編程中的核心概念純函數(shù)、柯里化以及組合函數(shù)。
純函數(shù)
純函數(shù)的概念
對(duì)于純函數(shù)的定義,維基百科中是這樣描述的:在程序設(shè)計(jì)中,若函數(shù)符合以下條件,那么這個(gè)函數(shù)被稱之為純函數(shù)。
- 此函數(shù)在相同的輸入值時(shí),需產(chǎn)生相同的輸出;
- 函數(shù)的輸入和輸出值以外的其他隱藏信息或狀態(tài)無關(guān),也和由I/O設(shè)備產(chǎn)生的外部輸出無關(guān);
- 該函數(shù)不能有語義上可觀察的函數(shù)副作用,諸如“觸發(fā)事件”,使輸出設(shè)備輸出,或更改輸出值以外物件的內(nèi)容等;
對(duì)以上描述總結(jié)就是:
- 對(duì)于相同的輸入,永遠(yuǎn)會(huì)得到相同的輸出;
- 在函數(shù)的執(zhí)行過程中,沒有任何可觀察的副作用;
- 同時(shí)也不依賴外部環(huán)境的狀態(tài);
副作用
上面提到了一個(gè)詞叫“副作用”,那么什么是副作用呢?
- 通常我們所說的副作用大多數(shù)是指藥會(huì)產(chǎn)生的副作用;
- 而在計(jì)算機(jī)科學(xué)中,副作用指在執(zhí)行一個(gè)函數(shù)時(shí),除了得到函數(shù)的返回值以外,還在函數(shù)調(diào)用時(shí)產(chǎn)生了附加的影響,比如修改了全局變量的狀態(tài),修改了傳入的參數(shù)或得到了其它的輸出內(nèi)容等;
純函數(shù)案例
- 編寫一個(gè)求和的函數(shù)sum,只要我們輸入了固定的值,sum函數(shù)就會(huì)給我們返回固定的結(jié)果,且不會(huì)產(chǎn)生任何副作用。
function sum(a, b) {
return a + b
}
const res = sum(10, 20)
console.log(res) // 30- 以下的sum函數(shù)雖然對(duì)于固定的輸入也會(huì)返回固定的輸出,但是函數(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ù)組的兩個(gè)方法slice和splice。
1.slice方法就是一個(gè)純函數(shù),因?yàn)閷?duì)于同一個(gè)數(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方法不是一個(gè)純函數(shù),因?yàn)樗淖兞嗽瓟?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 ]
柯里化
柯里化的概念
對(duì)于柯里化的定義,維基百科中是這樣解釋的:
- 柯里化是指把接收多個(gè)參數(shù)的函數(shù),變成接收一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接收余下的參數(shù),而且返回結(jié)果的新函數(shù)的技術(shù);
- 柯里化聲稱**“如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個(gè)函數(shù)”**;
總結(jié):只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩余的參數(shù)的過程就稱之為柯里化。
函數(shù)柯里化的過程
編寫一個(gè)普通的三值求和函數(shù):
function sum(x, y, z) {
return x + y + z
}
const res = sum(10, 20, 30)
console.log(res) // 60將以上求和函數(shù)柯里化得:
- 將傳入的三個(gè)參數(shù)進(jìn)行拆解,依次返回一個(gè)函數(shù),并傳入一個(gè)參數(shù);
- 在保證同樣功能的同時(shí),其調(diào)用方式卻發(fā)生了變化;
- 注意:在拆解參數(shù)時(shí),不一定非要將參數(shù)拆成一個(gè)個(gè)的,也可以拆成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ù)柯里化的特點(diǎn)及應(yīng)用
- **讓函數(shù)的職責(zé)更加單一。**柯里化可以實(shí)現(xiàn)讓一個(gè)函數(shù)處理的問題盡可能的單一,而不是將一大堆邏輯交給一個(gè)函數(shù)來處理。
1.將上面的三值求和函數(shù)增加一個(gè)需求,在計(jì)算結(jié)果之前給每個(gè)值加上2,先看看不使用柯里化的實(shí)現(xiàn)效果:
function sum(x, y, z) {
x = x + 2
y = y + 2
z = z + 2
return x + y + z
}2.柯里化的實(shí)現(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ù)柯里化后,讓我們對(duì)每個(gè)參數(shù)的處理更加單一
- **提高函數(shù)參數(shù)邏輯復(fù)用。**同樣使用上面的求和函數(shù),增加另一個(gè)需求,固定第一個(gè)參數(shù)的值為10,直接看柯里化的實(shí)現(xiàn)效果吧,后續(xù)函數(shù)調(diào)用時(shí)第一個(gè)參數(shù)值都為10的話,就可以直接調(diào)用sum10函數(shù)了。
function sum(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
}
const sum10 = sum(10) // 指定第一個(gè)參數(shù)值為10的函數(shù)
const res = sum10(20)(30)
console.log(res) // 60自動(dòng)柯里化函數(shù)的實(shí)現(xiàn)
function autoCurrying(fn) {
// 1.拿到當(dāng)前需要柯里化函數(shù)的參數(shù)個(gè)數(shù)
const fnLen = fn.length
// 2.定義一個(gè)柯里化之后的函數(shù)
function curried\_1(...args1) {
// 2.1拿到當(dāng)前傳入?yún)?shù)的個(gè)數(shù)
const argsLen = args1.length
// 2.1.將當(dāng)前傳入?yún)?shù)個(gè)數(shù)和fn需要的參數(shù)個(gè)數(shù)進(jìn)行比較
if (argsLen >= fnLen) {
// 如果當(dāng)前傳入的參數(shù)個(gè)數(shù)已經(jīng)大于等于fn需要的參數(shù)個(gè)數(shù)
// 直接執(zhí)行fn,并在執(zhí)行時(shí)綁定this,并將對(duì)應(yīng)的參數(shù)數(shù)組傳入
return fn.apply(this, args1)
} else {
// 如果傳入的參數(shù)不夠,說明需要繼續(xù)返回函數(shù)來接收參數(shù)
function curried\_2(...args2) {
// 將參數(shù)進(jìn)行合并,遞歸調(diào)用curried\_1,直到參數(shù)達(dá)到fn需要的參數(shù)個(gè)數(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ā)過程中一種對(duì)函數(shù)的使用技巧、模式。對(duì)某一個(gè)數(shù)據(jù)進(jìn)行函數(shù)調(diào)用,執(zhí)行兩個(gè)函數(shù),這兩個(gè)函數(shù)需要依次執(zhí)行,所以需要將這兩個(gè)函數(shù)組合起來,自動(dòng)依次調(diào)用,而這個(gè)過程就叫做函數(shù)的組合,組合形成的函數(shù)就叫做組合函數(shù)。
需求:對(duì)一個(gè)數(shù)字先進(jìn)行乘法運(yùn)算,再進(jìn)行平方運(yùn)算。
- 一般情況下,需要先定義兩個(gè)函數(shù),然后再對(duì)其依次調(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實(shí)現(xiàn)一個(gè)組合函數(shù),將duoble和square兩個(gè)函數(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實(shí)現(xiàn)一個(gè)自動(dòng)組合函數(shù)的函數(shù):
function autoComposeFn(...fns) {
// 1.拿到需要組合的函數(shù)個(gè)數(shù)
const fnsLen = fns.length
// 2.對(duì)傳入的函數(shù)進(jìn)行邊界判斷,所有參數(shù)必須為函數(shù)
for (let i = 0; i < fnsLen; i++) {
if (typeof fns[i] !== 'function') {
throw TypeError('The argument passed must be a function.')
}
}
// 3.定義一個(gè)組合之后的函數(shù)
function composeFn(...args) {
// 3.1.拿到第一個(gè)函數(shù)的返回值
let result = fns[0].apply(this, args)
// 3.1.判斷傳入的函數(shù)個(gè)數(shù)
if (fnsLen === 1) {
// 如果傳入的函數(shù)個(gè)數(shù)為一個(gè),直接將結(jié)果返回
return result
} else {
// 如果傳入的函數(shù)個(gè)數(shù) >= 2
// 依次將函數(shù)取出進(jìn)行調(diào)用,將上一個(gè)函數(shù)的返回值作為參數(shù)傳給下一個(gè)函數(shù)
// 從第二個(gè)函數(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)隨機(jī)點(diǎn)名器
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)隨機(jī)點(diǎn)名器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
讓網(wǎng)頁根據(jù)不同IE版本顯示不同的內(nèi)容
在上一篇blog 《IE8里判斷當(dāng)前網(wǎng)頁顯示模式》里面提到IE有不同的顯示模式以及如何用Javascript 來動(dòng)態(tài)判定。 Web開發(fā)者可以根據(jù)不同顯示模式導(dǎo)入不同的內(nèi)容。2009-02-02
解決Layui數(shù)據(jù)表格顯示無數(shù)據(jù)提示的問題
今天小編就為大家分享一篇解決Layui數(shù)據(jù)表格顯示無數(shù)據(jù)提示的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11
JavaScript插入動(dòng)態(tài)樣式實(shí)現(xiàn)代碼
能夠把CSS樣式包含到HTML頁面中的元素有兩個(gè)。其中,<link>元素用于包含來自外部的文件,而<style>元素用于指定嵌入的樣式2012-02-02
JS/jQuery實(shí)現(xiàn)簡單的開關(guān)燈效果【案例】
這篇文章主要介紹了JS/jQuery實(shí)現(xiàn)簡單的開關(guān)燈效果,結(jié)合具體實(shí)例形式分析了javascript/jQuery事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-02-02
JS實(shí)用的帶停頓的逐行文本循環(huán)滾動(dòng)效果實(shí)例
下面小編就為大家?guī)硪黄狫S實(shí)用的帶停頓的逐行文本循環(huán)滾動(dòng)效果實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
javascript-簡單的計(jì)算器實(shí)現(xiàn)步驟分解(附圖)
輸入內(nèi)容的判斷,對(duì)于事件對(duì)象的來源的判斷以及數(shù)學(xué)運(yùn)算“+,-,*,/”的使用,感興趣的朋友可以學(xué)習(xí)下2013-05-05

