溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)

發(fā)布時(shí)間:2020-07-03 20:53:34 來(lái)源:網(wǎng)絡(luò) 閱讀:1707 作者:大史不說(shuō)話 欄目:開(kāi)發(fā)技術(shù)

javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)

javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)

一. 箭頭函數(shù)

箭頭函數(shù)是ES6語(yǔ)法中加入的新特性,而它也是許多開(kāi)發(fā)者對(duì)ES6僅有的了解,每當(dāng)面試?yán)锉粏?wèn)到關(guān)于ES6里添加了哪些新特性?”這種問(wèn)題的時(shí)候,幾乎總是會(huì)拿箭頭函數(shù)來(lái)應(yīng)付。箭頭函數(shù),=>,沒(méi)有自己的this , arguments , super , new.target ,“書(shū)寫(xiě)簡(jiǎn)便,沒(méi)有this”在很長(zhǎng)一段時(shí)間內(nèi)涵蓋了大多數(shù)開(kāi)發(fā)者對(duì)于箭頭函數(shù)的全部認(rèn)知(當(dāng)然也包括我自己),如果只是為了簡(jiǎn)化書(shū)寫(xiě),把=>按照function關(guān)鍵字來(lái)解析就好了,何必要弄出一個(gè)跟普通函數(shù)特性不一樣的符號(hào)呢?答案就是:函數(shù)式編程(Functional Programming)

如果你了解javascript這門(mén)語(yǔ)言就知道,它是沒(méi)有類(lèi)這個(gè)東西的,ES6新加入的Class關(guān)鍵字,也不過(guò)是語(yǔ)法糖而已,我們不斷被要求使用面向?qū)ο缶幊?/strong>的思想來(lái)使用javascript,定義很多類(lèi),用復(fù)雜的原型鏈機(jī)制去模擬類(lèi),是因?yàn)楦嗟拈_(kāi)發(fā)者能夠習(xí)慣這種描述客觀世界的方式,《你不知道的javascript》中就明確指出原型鏈的機(jī)制其實(shí)只是實(shí)現(xiàn)了一種功能委托機(jī)制,即便不使用面向?qū)ο笾械母拍钊ッ枋鏊?,這也是一種合乎邏輯的語(yǔ)言設(shè)計(jì)方案,并不會(huì)造成巨大的認(rèn)知障礙。但需要明確的是,面向?qū)ο?/strong>并不是javascript唯一的使用方式。

當(dāng)然我也是接觸到【函數(shù)式編程】的思想后才意識(shí)到,我并不是說(shuō)【函數(shù)式編程】優(yōu)于【面向?qū)ο蟆?/strong>,每一種編程思想都有其適用的范圍,但它的確向我展示了另一種對(duì)編程的認(rèn)知方式,而且在流程控制的清晰度上,它的確比面向?qū)ο?/strong>更棒,它甚至讓我開(kāi)始覺(jué)得,這才是javascript該有的打開(kāi)方式。

如果你也曾以為【函數(shù)式編程】就是“用箭頭函數(shù)把函數(shù)寫(xiě)的精簡(jiǎn)一些”,如果你也被各種復(fù)雜的this綁定弄的暈頭轉(zhuǎn)向,那么就一起來(lái)看看這個(gè)胖箭頭指向的新世界——Functional Programming吧!

二. 更貼近本能的思維方式

假如有這樣一個(gè)題目:

javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)

在傳統(tǒng)編程中,你的編碼過(guò)程大約是這樣:

let resolveYX = (x) => 3*x*x + 2*x + 1;
let resolveZY = (y) => 4*y*y*y + 5*y*y + 6;
let resolveRZ = (z) => (2*z*z - 4)/3;
let y = resolveYX(2);
let z = resolveZY(y);
let result = resolveRZ(z);

我們大多時(shí)候采用的方式是把程序的執(zhí)行細(xì)節(jié)用程序語(yǔ)言描述出來(lái)。但是如果你把這道題拿給一個(gè)不懂編程的學(xué)生來(lái)做,就會(huì)發(fā)現(xiàn)大多數(shù)時(shí)候他們的做法會(huì)是下面的樣子:

javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)

先對(duì)方程進(jìn)行合并和簡(jiǎn)化,最后再代入數(shù)值進(jìn)行計(jì)算得到結(jié)果就可以了。有沒(méi)有發(fā)現(xiàn)事實(shí)上你自己在不寫(xiě)代碼的時(shí)候也是這樣做的,因?yàn)槟愫芮宄切┲虚g變量對(duì)于得到正確的結(jié)果來(lái)說(shuō)沒(méi)有什么意義,而這樣解題效率更高,尤其是當(dāng)前面的環(huán)節(jié)和后面的環(huán)節(jié)可以抵消掉某些互逆的運(yùn)算時(shí),這樣合并的好處可想而知。

而今天的主角【函數(shù)式編程】,可以看做是這種思維方式在程序設(shè)計(jì)中的應(yīng)用,我并不建議非數(shù)學(xué)專業(yè)的作者從范疇論的角度去解釋函數(shù)式編程,因?yàn)樾g(shù)語(yǔ)運(yùn)用的準(zhǔn)確性會(huì)造成難以評(píng)估的影響,很可能達(dá)不到技術(shù)交流的目的,反而最終誤人子弟。

三. 函數(shù)式編程

假如對(duì)某個(gè)需求的實(shí)現(xiàn),需要傳入x,然后經(jīng)歷3個(gè)步驟后得到一個(gè)答案y,你會(huì)怎樣來(lái)實(shí)現(xiàn)呢?

3.1 傳統(tǒng)代碼的實(shí)現(xiàn)

這樣一個(gè)需求在傳統(tǒng)編程中最容易想到的就是鏈?zhǔn)秸{(diào)用:

function Task(value){
    this.value = value;
}

Task.prototype.step = function(fn){
    let _newValue = fn(this.value);
    return new Task(_newValue);
}

y = (new Task(x)).step(fn1).step(fn2).step(fn3);

你或許在jQuery中經(jīng)常見(jiàn)到這樣的用法,或者你已經(jīng)意識(shí)到上面的函數(shù)實(shí)際上就是Promise的簡(jiǎn)化原型(關(guān)于Promise相關(guān)的知識(shí)可以看《javascript基礎(chǔ)修煉(7)——Promise,異步,可靠性》這篇文章),只不過(guò)我們把每一步驟包裹在了Task這個(gè)容器里,每個(gè)動(dòng)作執(zhí)行完以后返回一個(gè)新的Task容器,里面裝著上一個(gè)步驟返回的結(jié)果。

3.2 函數(shù)式代碼推演

【函數(shù)式編程】,我們不再采用程序語(yǔ)言按照步驟來(lái)復(fù)現(xiàn)一個(gè)業(yè)務(wù)邏輯,而是換一個(gè)更為抽象的角度,用數(shù)學(xué)的眼光看待所發(fā)生的事情。那么上面的代碼實(shí)際上所做的事情就是:

通過(guò)一系列變換操作,講一個(gè)數(shù)據(jù)集x變成了數(shù)據(jù)集y

有沒(méi)有一點(diǎn)似曾相識(shí)的感覺(jué)?沒(méi)錯(cuò),這就是我們熟知的【方程】,或者【映射】:
$$
y=f(x)
$$
我們將原來(lái)的代碼換個(gè)樣子,就更容易看出來(lái)了:

function prepare(){
    return function (x){
        return (new Task(x)).step(fn1).step(fn2).step(fn3);
    }    
}

let f = prepare();
let y = f(x);

上面的例子中,通過(guò)高階函數(shù)prepare( )將原來(lái)的函數(shù)改變?yōu)橐粋€(gè)延遲執(zhí)行的,等待接收一個(gè)參數(shù)x并啟動(dòng)一系列處理流程的新函數(shù)。再繼續(xù)進(jìn)行代碼轉(zhuǎn)換,再來(lái)看一下f(x)執(zhí)行到即將結(jié)束時(shí)的暫態(tài)狀況:

//fn2Result是XX.step(fn2)執(zhí)行完后返回的結(jié)果(值和方法都包含在Task容器中)
fn2Result.step(fn3);

上面的語(yǔ)句中,實(shí)際上變量只有fn2Result,step()方法和fn10都是提前定義好的,那么用函數(shù)化的思想來(lái)進(jìn)行類(lèi)比,這里也是實(shí)現(xiàn)了一個(gè)數(shù)據(jù)集x1到數(shù)據(jù)集y1的映射,所以它也可以被抽象為y = f ( x )的模式:

//先生成一個(gè)用于生成新函數(shù)的高階函數(shù),來(lái)實(shí)現(xiàn)局部調(diào)用
let goStep = function(fn){
    return function(params){
        let value = fn(params.value);
        return new Task(value);
    }
}
//fn2Result.step(fn3)這一句將被轉(zhuǎn)換為如下形式
let requireFn2Result = goStep(fn3);

此處的requireFn2Result( )方法,只接受一個(gè)由前置步驟執(zhí)行結(jié)束后得到的暫態(tài)結(jié)果,然后將其關(guān)鍵屬性value傳入fn3進(jìn)行運(yùn)算并傳回一個(gè)支持繼續(xù)鏈?zhǔn)秸{(diào)用的容器。我們來(lái)對(duì)代碼進(jìn)行一下轉(zhuǎn)換:

function prepare(){
    return function (x){
        let fn2Result = (new Task(x)).step(fn1).step(fn2); 
        return requireFn2Result(fn2Result);
    }    
}

同理繼續(xù)來(lái)簡(jiǎn)化前置步驟:

//暫時(shí)先忽略函數(shù)聲明的位置
let requireFn2Result = goStep(fn3);
let requireFn1Result = goStep(fn2);
let requireInitResult = goStep(fn1);

function prepare(){
    return function (x){
        let InitResult = new Task(x);
        return requireFn2Result(requireFn1Result(requireInitResult(InitResult)));
    }    
}

既然已經(jīng)這樣了,索性再向前一步,把new Task(x)也函數(shù)化好了:

let createTask = function(x){
    return new Task(x);
};

3.3 函數(shù)化的代碼

或許你已經(jīng)被上面的一系列轉(zhuǎn)化弄得暈頭轉(zhuǎn)向,我們暫停一下,來(lái)看看函數(shù)化后的代碼變成了什么樣子:

function prepare(){
    return function (x){
        return requireFn2Result(requireFn1Result(requireInitResult(createTask(x))));
    }    
}
let f = prepare();
let y = f(x);

這樣的編碼模式將核心業(yè)務(wù)邏輯在空間上放在一起,而把具體的實(shí)現(xiàn)封裝起來(lái),讓開(kāi)發(fā)者更容易看到一個(gè)需求實(shí)現(xiàn)過(guò)程的全貌。

3.4 休息一下

不知道你是否有注意到,在中間環(huán)節(jié)的組裝過(guò)程中,其實(shí)并沒(méi)有任何真實(shí)的數(shù)據(jù)出現(xiàn),我們只使用了暫態(tài)的抽象數(shù)據(jù)來(lái)幫助我們寫(xiě)出映射方法f的細(xì)節(jié),而隨后暫態(tài)的數(shù)據(jù)又被新的函數(shù)取代,逐級(jí)迭代,直到暫態(tài)數(shù)據(jù)最終指向了最外層函數(shù)的形參,你可以重新審視一下上面的推演過(guò)程來(lái)體會(huì)函數(shù)式編程帶來(lái)的變化,這個(gè)點(diǎn)是非常重要的。

3.5 進(jìn)一步抽象

3.3節(jié)中函數(shù)化的代碼中,存在一個(gè)很長(zhǎng)的嵌套調(diào)用,如果業(yè)務(wù)邏輯步驟過(guò)多,那么這行代碼會(huì)變得很長(zhǎng),同時(shí)也很難閱讀,我們需要通過(guò)一些手段將這些中間環(huán)節(jié)的函數(shù)展開(kāi)為一種扁平化的寫(xiě)法。

/**
*定義一個(gè)工具函數(shù)compose,接受兩個(gè)函數(shù)作為參數(shù),返回一個(gè)新函數(shù)
*新函數(shù)接受一個(gè)x作為入?yún)ⅲ缓髮?shí)現(xiàn)函數(shù)的迭代調(diào)用。
*/
var compose = function (f, g) {
    return function (x) {
        return f(g(x));
    }
};
/**
*升級(jí)版本的compose函數(shù),接受一組函數(shù),實(shí)現(xiàn)左側(cè)函數(shù)包裹右側(cè)函數(shù)的形態(tài)
*/
let composeEx = function (...args) {
    return (x)=>args.reduceRight((pre,cur)=>cur(pre),x);
}

看不懂的同學(xué)需要補(bǔ)補(bǔ)基礎(chǔ)課了,需要注意的是工具函數(shù)返回的仍然是一個(gè)函數(shù),我們使用上面的工具函數(shù)來(lái)重寫(xiě)一下3.3小節(jié)中的代碼:

let pipeline = composeEx(requireFn2Result,requireFn1Result,requireInitResult,createTask);
function prepare(){
    return function (x){
        return pipeline(x);
    }    
}
let f = prepare();
let y = f(x);

還要繼續(xù)?必須的,希望你還沒(méi)有抓狂。代碼中我們先執(zhí)行prepare( )方法來(lái)得到一個(gè)新函數(shù)ff執(zhí)行時(shí)接收一個(gè)參數(shù)x,然后把x傳入pipeline方法,并返回pipeline(x)。我們來(lái)進(jìn)行一下對(duì)比:

//prepare執(zhí)行后得到的新函數(shù)
let f = x => pipeline(x);

或許你已經(jīng)發(fā)現(xiàn)了問(wèn)題所在,這里的f函數(shù)相當(dāng)于pipeline方法的代理,但這個(gè)代理什么額外的動(dòng)作都沒(méi)有做,相當(dāng)于只是在函數(shù)調(diào)用棧中憑空增加了一層,但是執(zhí)行了相同的動(dòng)作。如果你能夠理解這一點(diǎn),就可以得出下面的轉(zhuǎn)化結(jié)果:

let f = pipeline;

是不是很神奇?順便提一下,它的術(shù)語(yǔ)叫做point free,當(dāng)你深入學(xué)習(xí)【函數(shù)式編程】時(shí)就會(huì)接觸到。

3.6 完整的轉(zhuǎn)換代碼

我們?cè)龠M(jìn)行一些簡(jiǎn)易的抽象和整理,然后得到完整的流程:

let composeEx = (...args) => (x) => args.reduceRight((pre,cur) =>cur(pre),x);
let getValue = (obj) => obj.value;
let createTask = (x) => new Task(x);
/*goStep執(zhí)行后得到的函數(shù)也滿足前面提到的“l(fā)et f=(x)=>g(x)”的形式,可以將其pointfree化.
let goStep = (fn)=>(params)=>composeEx(createTask, fn, getValue)(params);
let requireFn2Result = goStep(fn3);
*/
let requireFn2Result = composeEx(createTask,fn3,getValue);
let requireFn1Result = composeEx(createTask,fn2,getValue);
let requireInitResult = composeEx(createTask,fn1,getValue);
let pipeline = composeEx(requireFn2Result,requireFn1Result,requireInitResult,createTask);
let f = pipeline;
let y = f(x);

可以看到我們定義完方法后,像搭積木一樣把它們組合在一起,就得到了一個(gè)可以實(shí)現(xiàn)目標(biāo)功能的函數(shù)。

3.7 為什么它看起來(lái)變得更復(fù)雜了

如果只看上面的示例,的確是這樣的,上面的示例只是為了展示函數(shù)式編程讓代碼向著怎樣一個(gè)方向去變化而已,而并沒(méi)有展示出函數(shù)式編程的優(yōu)勢(shì),這種轉(zhuǎn)變和一個(gè)jQuery開(kāi)發(fā)者剛開(kāi)始使用諸如angular,vue,React框架時(shí)感受到的強(qiáng)烈不適感是很相似的,畢竟思想的轉(zhuǎn)變是非常困難的。

面向?qū)ο缶幊?/strong>寫(xiě)出的代碼看起來(lái)就像是一個(gè)巨大的關(guān)系網(wǎng)和邏輯流程圖,比如連續(xù)讀其中10行代碼,你或許能夠很清晰地看到某個(gè)步驟執(zhí)行前和執(zhí)行后程序的狀態(tài),但是卻很難看清整體的業(yè)務(wù)邏輯流程;而函數(shù)式編程正好是相反的,你可以在短短的10行代碼中看到整個(gè)業(yè)務(wù)流程,當(dāng)你想去深究某個(gè)具體步驟時(shí),再繼續(xù)展開(kāi),另一方面,關(guān)注數(shù)據(jù)和函數(shù)組合可以將你從復(fù)雜的this和對(duì)象的關(guān)系網(wǎng)中解放出來(lái)。

四. 兩個(gè)主角

數(shù)據(jù)函數(shù)【函數(shù)式編程】中的兩大核心概念,它為我們提供了用數(shù)學(xué)的眼光看世界的獨(dú)特視角,同時(shí)它也更程序員該有的思維模式——設(shè)計(jì)程序,而不是僅僅是復(fù)現(xiàn)業(yè)務(wù)邏輯:

程序設(shè)計(jì) = 數(shù)據(jù)結(jié)構(gòu) + 算法   Vs   函數(shù)式編程 = 數(shù)據(jù) + 函數(shù)    

但為了更加安全有效地使用,它們和傳統(tǒng)編程中的同名概念相比多了一些限制。

函數(shù)Vs純函數(shù)

函數(shù)式編程中所傳遞和使用的函數(shù),被要求為【純函數(shù)】。純函數(shù)需要滿足如下兩個(gè)條件:

  • 只依賴自己的參數(shù)
  • 執(zhí)行過(guò)程沒(méi)有副作用

為什么純函數(shù)只能依賴自己的參數(shù)?因?yàn)橹挥羞@樣,我們才不必在對(duì)函數(shù)進(jìn)行傳遞和組合的時(shí)候小心翼翼,生怕在某個(gè)環(huán)節(jié)弄丟了this的指向,如果this直接報(bào)錯(cuò)還好,如果指向了錯(cuò)誤的數(shù)據(jù),程序本身在運(yùn)行時(shí)也不會(huì)報(bào)錯(cuò),這種情況的調(diào)試是非常令人頭疼的,除了逐行運(yùn)行并檢查對(duì)應(yīng)數(shù)據(jù)的狀態(tài),幾乎沒(méi)什么高效的方法。面向?qū)ο蟮木幊讨校覀儾坏貌皇褂煤芏?code>bind函數(shù)來(lái)綁定一個(gè)函數(shù)的this指向,而純函數(shù)就不存在這樣的問(wèn)題。來(lái)看這樣兩個(gè)函數(shù):

var a = 1;
function inc(x){
    return a + x;
}
function pureInc(x){
    let a = 1;
    return x + a;
}

對(duì)于inc這個(gè)函數(shù)來(lái)說(shuō),改變外部條件a的值就會(huì)造成inc函數(shù)對(duì)于同樣的入?yún)⒌玫讲煌慕Y(jié)果的情況,換言之在入?yún)⒋_定為3的前提下,每次執(zhí)行inc(3)得到的結(jié)果是不確定的,所以它是不純的。而pureInc函數(shù)就不依賴于外界條件的變化,pureInc(3)無(wú)論執(zhí)行多少次,無(wú)論外界參數(shù)如何變化,其輸出結(jié)果都是確定的。

在面向?qū)ο蟮木幊讨?,我們?xiě)的函數(shù)通常都不是純函數(shù),因?yàn)榫幊讨谢蚨嗷蛏俣夹枰诓煌暮瘮?shù)中共享一些標(biāo)記狀態(tài)的變量,我們更傾向與將其放在更高層的作用域里,通過(guò)標(biāo)識(shí)符的右查詢會(huì)沿作用域鏈尋找的機(jī)制來(lái)實(shí)現(xiàn)數(shù)據(jù)共享。

什么是函數(shù)的副作用呢?一個(gè)函數(shù)執(zhí)行過(guò)程對(duì)產(chǎn)生了外部可觀察的變化那么就說(shuō)這個(gè)函數(shù)是有副作用的。最常見(jiàn)的情況就是函數(shù)接受一個(gè)對(duì)象作為參數(shù),但是在函數(shù)內(nèi)部對(duì)其進(jìn)行了修改,javascript中函數(shù)在傳遞對(duì)象參數(shù)時(shí)會(huì)將其地址傳入調(diào)用的函數(shù),所以函數(shù)內(nèi)部所做的修改也會(huì)同步反應(yīng)到函數(shù)外部,這種副作用會(huì)在函數(shù)組合時(shí)造成最終數(shù)據(jù)的不可預(yù)測(cè)性,因?yàn)橛嘘P(guān)某個(gè)對(duì)象的函數(shù)都有可能得到不確定的輸出。

數(shù)據(jù)Vs不可變數(shù)據(jù)

javascript中的對(duì)象很強(qiáng)大也很靈活,可并不是所有的場(chǎng)景中我們都需要這種靈活性。來(lái)看這樣一個(gè)例子:

let a = {
    name:'tony'
}
let b = a;
modify(b);
console.log(a.name);

我們無(wú)法確定上面的輸出結(jié)果,因?yàn)?code>a和b這兩個(gè)標(biāo)識(shí)符指向了堆中的相同的地址,可外界無(wú)法知道在modify函數(shù)中是否對(duì)b的屬性做出了修改。有些場(chǎng)景中為了使得邏輯過(guò)程更加可靠,我們不希望后續(xù)的操作和處理對(duì)最原始的數(shù)據(jù)造成影響,這個(gè)時(shí)候我們很確定需要拿到一個(gè)數(shù)據(jù)集的復(fù)制(比如拿到表格的總數(shù)據(jù),在實(shí)現(xiàn)某些過(guò)濾功能的時(shí)候,通常需要留存一個(gè)表格數(shù)據(jù)的備份,以便取消過(guò)濾時(shí)可以恢復(fù)原貌),這就引出了老生常談的深拷貝和淺拷貝的話題。

【深拷貝】是一種典型的防御性編程,因?yàn)樵跍\拷貝的機(jī)制下,修改對(duì)象屬性的時(shí)候會(huì)影響到所有指向它的標(biāo)識(shí)符,從而造成不可預(yù)測(cè)的結(jié)果。

javascript中,常見(jiàn)的深拷貝都是通過(guò)遞歸來(lái)實(shí)現(xiàn)的,然后利用語(yǔ)言特性做出一些代碼層面的優(yōu)化,例如各個(gè)第三方庫(kù)中的extend( )方法或者deepClone( )。可是當(dāng)一個(gè)結(jié)構(gòu)很深或者復(fù)雜度很高時(shí),深拷貝的耗時(shí)就會(huì)大幅增加,有的時(shí)候我們關(guān)注的可能只是數(shù)據(jù)結(jié)構(gòu)中的一部分,也就是說(shuō)新老對(duì)象中很大一部分?jǐn)?shù)據(jù)是一致的,可以共享的,但深拷貝過(guò)程中忽視了這種情況而簡(jiǎn)單粗暴地對(duì)整個(gè)對(duì)象進(jìn)行遞歸遍歷和克隆。

事實(shí)上【深拷貝】并不是防御性編程的唯一方法,FacebookImmutable.js就用不可變數(shù)據(jù)的思路來(lái)解決這個(gè)問(wèn)題,它將對(duì)象這種引用值變得更像原始值(javascript中的原始值創(chuàng)建后是不能修改的)。

//Immutable.js官網(wǎng)示例
 var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
 var map2 = map1.set('b', 50);
 map1.get('b'); // 2
 map2.get('b'); // 50

你可以查看【Immutable.js官方文檔】來(lái)了解如何使用它,通常它是結(jié)合React全家桶一起使用的。如果你對(duì)其實(shí)現(xiàn)原理感興趣,可以查看《深入探究Immutable.js的實(shí)現(xiàn)機(jī)制》一文或者查看其他資料,來(lái)了解一下Hash樹(shù)Trie樹(shù)是如何作為Immutable的算法基礎(chǔ)而被應(yīng)用的。

當(dāng)標(biāo)識(shí)符指向不變的數(shù)據(jù),當(dāng)函數(shù)沒(méi)有副作用,就可以大膽廣泛地使用函數(shù)式編程了

四. 前端的學(xué)習(xí)路線

  • javascript基礎(chǔ)

    如果你能夠很清楚高階函數(shù),柯里化反柯里化這些關(guān)鍵詞的含義和一般用途,并且至少了解Arraymapreduce方法做了什么事情,那么就可以進(jìn)行下一步。否則就需要好好復(fù)習(xí)一下javascript的基礎(chǔ)知識(shí)。在javascript中進(jìn)行函數(shù)式編程會(huì)反復(fù)涉及到這些基本技術(shù)的運(yùn)用。

  • 《javascript函數(shù)式編程指南》

    地址:https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/

    這是一本來(lái)自于gitbook的翻譯版的非常棒的開(kāi)源電子書(shū),這本書(shū)很棒,但是如果將函數(shù)式編程的相關(guān)知識(shí)分為初中高級(jí)的話,這本書(shū)似乎只涵蓋了初級(jí)和高級(jí),而省略了中級(jí)的部分,當(dāng)內(nèi)容涉及到范疇論和代數(shù)結(jié)構(gòu)的時(shí)候,理解難度會(huì)突然一下變得很大。當(dāng)你讀不懂的時(shí)候可以先停下來(lái),用下一個(gè)資料進(jìn)行過(guò)渡,然后回過(guò)頭來(lái)再繼續(xù)閱讀后續(xù)的部分。

    同時(shí)提一句,翻譯者@胡子大哈也是之前提及的那本著名的《React小書(shū)》的主要作者。

  • Ramda.js官網(wǎng)博文集

    地址:https://ramdajs.com/

    Ramda.jsjavascript提供了一系列函數(shù)式編程的工具函數(shù),但官網(wǎng)的《Thinking In Ramda》系列教程,是非常好的中級(jí)教程,結(jié)合Ramda的API進(jìn)行講解,讓開(kāi)發(fā)者更容易理解函數(shù)式編程,它正好彌補(bǔ)了前一個(gè)資料中沒(méi)有中級(jí)教程的問(wèn)題。

  • Ramda.js的API

    不得不說(shuō)很多前端開(kāi)發(fā)者都是從API開(kāi)始學(xué)習(xí)函數(shù)式編程的,但很快就會(huì)發(fā)現(xiàn)學(xué)了和沒(méi)學(xué)差不多,因?yàn)闆](méi)有理論基礎(chǔ),你很難知道該去使用它。就好像給了你最頂尖的工具,你也沒(méi)法一次性就做出好吃的牛排,因?yàn)槟悴粫?huì)做。

  • Rx.js和Immutable.js

    事實(shí)上筆者自己也還沒(méi)有進(jìn)行到這個(gè)階段的學(xué)習(xí),Rx.js是隸屬于Angular全家桶的,Immutable.js是隸屬于React全家桶的,即使在自己目前的工作中沒(méi)有直接使用到,你也應(yīng)該了解它們。

  • 代數(shù)結(jié)構(gòu)的理論基礎(chǔ)

    地址:https://github.com/fantasyland/fantasy-land

    當(dāng)你具備了基本的使用能力,想要更上一層樓的時(shí)候,就需要重新整合函數(shù)式編程的理論體系。這個(gè)項(xiàng)目用于解釋函數(shù)式編程的理論基礎(chǔ)中各類(lèi)術(shù)語(yǔ)及相關(guān)用途。

五. 小結(jié)

【函數(shù)式編程】為我們展現(xiàn)了javascript語(yǔ)言的另一種靈活性。

開(kāi)發(fā)人員會(huì)發(fā)現(xiàn)自己可以從更宏觀地角度來(lái)觀察整個(gè)業(yè)務(wù)流程,而不是往返于業(yè)務(wù)邏輯和實(shí)現(xiàn)細(xì)節(jié)之間。

測(cè)試人員會(huì)發(fā)現(xiàn)它很容易進(jìn)行單元測(cè)試,不僅因?yàn)樗募兒瘮?shù)特性,也因?yàn)閿?shù)據(jù)和動(dòng)作被分離了。

游戲玩家會(huì)發(fā)現(xiàn)它和自己在《我的世界》里用方塊來(lái)搭建世界就是這樣子的。

工程師會(huì)發(fā)現(xiàn)它和對(duì)照零件圖紙編寫(xiě)整個(gè)加工流水線的工藝流程時(shí)就是這樣做的。

數(shù)學(xué)家會(huì)說(shuō)用數(shù)學(xué)的思維是可以描述世界的(如果你接觸過(guò)數(shù)學(xué)建模應(yīng)該會(huì)更容易明白)。

【函數(shù)式編程】讓開(kāi)發(fā)者理解程序設(shè)計(jì)這件事本質(zhì)是是一種設(shè)計(jì),是一種創(chuàng)造行為,和其他通過(guò)組合功能單元而得到更強(qiáng)大的功能單元的行為沒(méi)有本質(zhì)區(qū)別。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI