從實現(xiàn)一個紅綠燈到函數(shù)式編程

我認為函數(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中的mapreduce近忙。這是最主要的概念竭业,剩下的概念則都是一些規(guī)定智润,幫助我們寫出更好的更健壯的函數(shù)式函數(shù),相信理解了我剛剛所說的例子未辆,再去理解其他概念會容易許多窟绷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咐柜,隨后出現(xiàn)的幾起案子兼蜈,更是在濱河造成了極大的恐慌,老刑警劉巖拙友,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭尝,死亡現(xiàn)場離奇詭異,居然都是意外死亡献宫,警方通過查閱死者的電腦和手機钥平,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姊途,“玉大人涉瘾,你說我怎么就攤上這事〗堇迹” “怎么了立叛?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贡茅。 經(jīng)常有香客問我秘蛇,道長,這世上最難降的妖魔是什么顶考? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任赁还,我火速辦了婚禮,結(jié)果婚禮上驹沿,老公的妹妹穿的比我還像新娘艘策。我一直安慰自己,他們只是感情好渊季,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布朋蔫。 她就那樣靜靜地躺著,像睡著了一般却汉。 火紅的嫁衣襯著肌膚如雪驯妄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天合砂,我揣著相機與錄音青扔,去河邊找鬼。 笑死,一個胖子當著我的面吹牛赎懦,可吹牛的內(nèi)容都是我干的雀鹃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼励两,長吁一口氣:“原來是場噩夢啊……” “哼黎茎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起当悔,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤傅瞻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盲憎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗅骄,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年饼疙,在試婚紗的時候發(fā)現(xiàn)自己被綠了溺森。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡窑眯,死狀恐怖屏积,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磅甩,我是刑警寧澤炊林,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站卷要,受9級特大地震影響渣聚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜僧叉,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一奕枝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彪标,春花似錦倍权、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽当船。三九已至题画,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間德频,已是汗流浹背苍息。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竞思。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓表谊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盖喷。 傳聞我的和親對象是個殘疾皇子爆办,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344