我認為函數(shù)式編程的本質(zhì)是把函數(shù)當作變量來使用笛粘。最近接觸了React
瓷炮,我們可以發(fā)現(xiàn)在React
中處處存在函數(shù)式編程的思想壤玫,我們將JSX
寫成一個函數(shù)的返回值,并且在render
中調(diào)用她們速缨。函數(shù)是對于過程的抽象,我們將過程抽象成一個函數(shù)代乃,然后根據(jù)不同的場景傳入不同的變量值從而得到不同的結(jié)果旬牲。而函數(shù)式編程是對于函數(shù)的抽象,我們把函數(shù)看作是一個變量搁吓,然后根據(jù)不同的場景傳入不同的函數(shù)值從而得到不同的結(jié)果原茅。
我希望可以通過實現(xiàn)一個小的demo
來講清楚這件事,這個demo
是我很久以前在某個技術(shù)網(wǎng)站看到的堕仔,具體的網(wǎng)址已經(jīng)記不清楚了擂橘,鑒于小伙伴們對于我所講的函數(shù)式編程的概念非常感興趣,所以我將這個例子翻了出來摩骨,自己將它重新實現(xiàn)一遍通贞。若是沒有講清楚或錯誤的地方朗若,則是受限于我的淺薄的技術(shù)功底,并希望被指正昌罩。
紅綠燈函數(shù)
客戶希望我們實現(xiàn)一個紅綠燈函數(shù)捡偏,每隔1秒鐘,變換一次信號峡迷,無限循環(huán)银伟。可以使用console.log來模擬信號燈亮的過程绘搞,即在控制臺打印'綠燈亮',1s后繼續(xù)打印'黃燈亮',1s后打印'紅燈亮'...無限循環(huán)
實現(xiàn)這個效果非常簡單彤避,直接上代碼
function semaphores() {
setTimeout(()=>{
console.log('綠燈亮');
setTimeout(() => {
console.log('黃燈亮');
setTimeout(() => {
console.log('紅燈亮');
semaphores();
}, 1000)
}, 1000)
},1000)
}
// delay 1s
// 綠燈亮
// delay 1s
// 黃燈亮
// delay 1s
// 紅燈亮
// delay 1s
// ...
但是很明顯,這樣的函數(shù)及其不優(yōu)雅夯辖,我們的紅綠燈只有三種顏色琉预,如果要是有十幾種顏色的霓虹燈閃來閃去的話,我們就要嵌套十幾層代碼了蒿褂。這樣可讀性實在是太差了圆米,于是我們希望可以把它優(yōu)化一下。
仔細分析一下我們的代碼就會發(fā)現(xiàn)啄栓,上面的demo有非常多的冗余代碼娄帖,setTimeout在一個函數(shù)中被重復使用了三次,每次只不過是一個嵌套再打印不同的紅黃綠燈而已昙楚,我們?yōu)槭裁床话压膊糠痔崛〕鰜砟亟伲堪鸭t黃綠燈提取成變量,代碼質(zhì)量會好很多堪旧。
so出現(xiàn)了下列的代碼:
const TRAFFIC_LIGHT = [
'綠燈',
'黃燈',
'紅燈'
]
function semaphores(lightList = [], count = 0) {
// 控制器
let lightLength = lightList.length;
return setTimeout(() => {
console.log(`${lightList[count % lightLength]}`);
count ++;
semaphores(lightList,count);
}, 1000)
}
// delay 1s
// 綠燈亮
// delay 1s
// 黃燈亮
// delay 1s
// 紅燈亮
// delay 1s
// ...
我們還可以進一步優(yōu)化削葱,同時將時間抽離:
const TRAFFIC_LIGHT = [
['綠燈', 1000],
['黃燈', 2000],
['紅燈', 3000]
]
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 綠燈亮
// delay 2s
// 黃燈亮
// delay 3s
// 紅燈亮
// delay 1s
// ...
我們可以更加容易修改我們的代碼,以應對將來可能的需求變更淳梦。這也是我們常見的函數(shù)形式析砸。變量只是 Number、String爆袍、Object首繁、undefined、null螃宙、Boolean蛮瞄,但是我們再次將問題變得復雜一些:
我們的紅綠燈效果良好,客戶非常滿意谆扎,并且希望擴展應用范圍挂捅。為了應對可能到來的困惑,我們需要在每次信號燈亮起的時候添加上一條提示語堂湖,提醒剩余時間闲先。不同的應用場景將有不同的提示語状土。
這也非常簡單,我們只需要多加一條語句就可以完成:
const TRAFFIC_LIGHT = [
['綠燈', 1000],
['黃燈', 2000],
['紅燈', 3000]
]
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// add here!!!
console.log('這是一條提示語');
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 綠燈亮
// 這是一條提示語
// delay 2s
// 黃燈亮
// 這是一條提示語
// delay 3s
// 紅燈亮
// 這是一條提示語
// delay 1s
// ...
但隨著我們的應用場景進一步增多伺糠,我們的紅綠燈將廣泛的用于醫(yī)療蒙谓、運輸、零售行業(yè)训桶,對于這些行業(yè)我們需要在信號燈亮起時擁有不同的操作累驮,有的是發(fā)出聲響、有的是打印提示語舵揭、有的是發(fā)出警報谤专、有的是打開開關(guān)...
那么我們是不是需要把上面的那份代碼到處拷貝一下,然后修改位于16行的代碼午绳,分別把它修改為發(fā)聲置侍、打印、報警拦焚、開關(guān)控制...
顯然一般的函數(shù)已經(jīng)無法滿足我們的需求了蜡坊,這時候我們就需要函數(shù)式編程。
函數(shù)式編程
我上面只是為了引出函數(shù)式編程的例子赎败,所以舉例夸張了一些秕衙。事實上函數(shù)式編程并不是非常高大上的概念,它就和面向?qū)ο竺弧⒚嫦蜻^程一樣灾梦,只是一種編程的范式峡钓,在實際中擁有著非常廣泛的應用(例如JSX)
就像我一開始提到的那樣妓笙,函數(shù)式編程的核心不過是將函數(shù)當作變量那樣來使用。我們希望改造一下我們的紅綠燈函數(shù)能岩,使我們的函數(shù)在信號燈亮后寞宫,可以在不同的場景使用不同的參數(shù),我們來設(shè)計一下這個函數(shù).首先我們需要傳入一個函數(shù)拉鹃,所以我們在形參列表中加入一個handler
的變量辈赋,便于我們使用它。然后在我們希望使用它的地方使用就好了膏燕。
const TRAFFIC_LIGHT = [
['綠燈', 1000],
['黃燈', 2000],
['紅燈', 3000]
]
// 請注意我們在這里加入了一個參數(shù)handler
function semaphores(lightList = [], handler, count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// 在這里使用它
handler && handler(light, time);
count ++;
handleSemaphores();
}, time)
}
}
我們可以這樣使用上面的函數(shù):
// 我們只需要在不同的場景改變不同的handler就可以了
const handler = function (light, time) {
console.log('函數(shù)式編程好爽啊');
console.log(`${light}亮${time}毫秒~~`)
}
const doTrafficLight = semaphores(TRAFFIC_LIGHT, handler);
const trafficTimeHandler = doTrafficLight();
// delay 1s
// 綠燈亮
// 函數(shù)式編程好爽啊
// 綠燈亮1000毫秒~~
// delay 2s
// 黃燈亮
// 函數(shù)式編程好爽啊
// 黃燈亮2000毫秒~~
// delay 3s
// 紅燈亮
// 函數(shù)式編程好爽啊
// 紅燈亮3000毫秒~~
// ...
我們更可以優(yōu)化一下上面的函數(shù)钥屈,使得不同等亮起時使用不同的行為函數(shù):
const TRAFFIC_LIGHT = [
['綠燈', 1000, function() {
// do something...
}],
['黃燈', 2000, function() {
// do something...
}],
['紅燈', 3000, function() {
// do something...
}]
]
// 我們?nèi)サ袅薶andler!!!!
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time, handler] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// 在這里使用它
handler && handler(light, time);
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 綠燈亮
// dosomething1...
// delay 2s
// 黃燈亮
// dosomething2...
// delay 3s
// 紅燈亮
// dosomething3...
// ...
函數(shù)式編程的抽象程度更高,它是對于過程的抽象坝辫。如果沒有傳入的參數(shù)作為支撐篷就,那么這個函數(shù)毫無意義,比較明顯的例子是JavaScript
中的map
和reduce
近忙。這是最主要的概念竭业,剩下的概念則都是一些規(guī)定智润,幫助我們寫出更好的更健壯的函數(shù)式函數(shù),相信理解了我剛剛所說的例子未辆,再去理解其他概念會容易許多窟绷。