国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

翻譯連載 | JavaScript輕量級(jí)函數(shù)式編程-第4章:組合函數(shù) |《你不知道的JS》姊妹篇

JowayYoung / 3096人閱讀

摘要:把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實(shí)都是冷卻切割包裝糖果中的一步。在該章節(jié)中,我們將會(huì)用糖果工廠的類比來(lái)解釋什么是組合。糖果工廠靠這套流程運(yùn)營(yíng)的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長(zhǎng)點(diǎn)。

原文地址:Functional-Light-JS

原文作者:Kyle Simpson-《You-Dont-Know-JS》作者

關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),是 JavaScript 中最嚴(yán)謹(jǐn)?shù)倪壿嫛=?jīng)過(guò)捶打磨練,成就了本書的中文版。本書包含了函數(shù)式編程之精髓,希望可以幫助大家在學(xué)習(xí)函數(shù)式編程的道路上走的更順暢。比心。

譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao

JavaScript輕量級(jí)函數(shù)式編程 第 4 章:組合函數(shù)

到目前為止,我希望你能更輕松地理解在函數(shù)式編程中使用函數(shù)意味著什么。

一個(gè)函數(shù)式編程者,會(huì)將他們程序中的每一個(gè)函數(shù)當(dāng)成一小塊簡(jiǎn)單的樂(lè)高部件。他們能一眼辨別出藍(lán)色的 2x2 方塊,并準(zhǔn)確地知道它是如何工作的、能用它做些什么。當(dāng)構(gòu)建一個(gè)更大、更復(fù)雜的樂(lè)高模型時(shí),當(dāng)每一次需要下一塊部件的時(shí)候,他們能夠準(zhǔn)確地從備用部件中找到這些部件并拿過(guò)來(lái)使用。

但有些時(shí)候,你把藍(lán)色 2x2 的方塊和灰色 4x1 的方塊以某種形式組裝到一起,然后意識(shí)到:“這是個(gè)有用的部件,我可能會(huì)常用到它”。

那么你現(xiàn)在想到了一種新的“部件”,它是兩種其他部件的組合,在需要的時(shí)候能觸手可及。這時(shí)候,將這個(gè)藍(lán)黑色 L 形狀的方塊組合體放到需要使用的地方,比每次分開(kāi)考慮兩種獨(dú)立方塊的組合要有效的多。

函數(shù)有多種多樣的形狀和大小。我們能夠定義某種組合方式,來(lái)讓它們成為一種新的組合函數(shù),程序中不同的部分都可以使用這個(gè)函數(shù)。這種將函數(shù)一起使用的過(guò)程叫做組合。

輸出到輸入

我們已經(jīng)見(jiàn)過(guò)幾種組合的例子。比如,在第 3 章中,我們對(duì) unary(..) 的討論包含了如下表達(dá)式:unary(adder(3))。仔細(xì)想想這里發(fā)生了什么。

為了將兩個(gè)函數(shù)整合起來(lái),將第一個(gè)函數(shù)調(diào)用產(chǎn)生的輸出當(dāng)做第二個(gè)函數(shù)調(diào)用的輸入。在 unary(adder(3)) 中,adder(3) 的調(diào)用返回了一個(gè)值(值是一個(gè)函數(shù));該值被直接作為一個(gè)參數(shù)傳入到 unary(..) 中,同樣的,這個(gè)調(diào)用返回了一個(gè)值(值為另一個(gè)函數(shù))。

讓我們回放一下過(guò)程并且將數(shù)據(jù)流動(dòng)的概念視覺(jué)化,是這個(gè)樣子:

functionValue <-- unary <-- adder <-- 3

3adder(..) 的輸入。而 adder(..) 的輸出是 unary(..) 的輸入。unary(..) 的輸出是 functionValue。 這就是 unary(..)adder(..) 的組合。

把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實(shí)都是冷卻、切割、包裝糖果中的一步。在該章節(jié)中,我們將會(huì)用糖果工廠的類比來(lái)解釋什么是組合。

讓我們一步一步的來(lái)了解組合。首先假設(shè)你程序中可能存在這么兩個(gè)實(shí)用函數(shù)。

function words(str) {
    return String( str )
        .toLowerCase()
        .split( /s|/ )
        .filter( function alpha(v){
            return /^[w]+$/.test( v );
        } );
}

function unique(list) {
    var uniqList = [];

    for (let i = 0; i < list.length; i++) {
        // value not yet in the new list?
        if (uniqList.indexOf( list[i] ) === -1 ) {
            uniqList.push( list[i] );
        }
    }

    return uniqList;
}

使用這兩個(gè)實(shí)用函數(shù)來(lái)分析文本字符串:

var text = "To compose two functions together, pass the 
output of the first function call as the input of the 
second function call.";

var wordsFound = words( text );
var wordsUsed = unique( wordsFound );

wordsUsed;
// ["to","compose","two","functions","together","pass",
// "the","output","of","first","function","call","as",
// "input","second"]

我們把 words(..) 輸出的數(shù)組命名為 wordsFoundunique(..) 的輸入也是一個(gè)數(shù)組,因此我們可以將 wordsFound 傳入給它。

讓我們重新回到糖果工廠的流水線:第一臺(tái)機(jī)器接收的“輸入”是融化的巧克力,它的“輸出”是一堆成型且冷卻的巧克力。流水線上的下一個(gè)機(jī)器將這堆巧克力作為它的“輸入”,它的“輸出”是一片片切好的巧克力糖果。下一步就是,流水線上的另一臺(tái)機(jī)器將這些傳送帶上的小片巧克力糖果處理,并輸出成包裝好的糖果,準(zhǔn)備打包和運(yùn)輸。

糖果工廠靠這套流程運(yùn)營(yíng)的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長(zhǎng)點(diǎn)。

為了跟上更多糖果的生產(chǎn)需求,他們決定拿掉傳送帶這么個(gè)玩意,直接把三臺(tái)機(jī)器疊在一起,這樣第一臺(tái)的輸出閥就直接和下一臺(tái)的輸入閥直接連一起了。這樣第一臺(tái)機(jī)器和第二臺(tái)機(jī)器之間,就再也不會(huì)有一堆巧克力在傳送帶上慢吞吞的移動(dòng)了,并且也不會(huì)有空間浪費(fèi)和隆隆的噪音聲了。

這項(xiàng)革新為工廠節(jié)省了很大的空間,所以管理者很高興,他們每天能夠造更多的糖果了!

等價(jià)于這種升級(jí)后的糖果工廠配置的代碼跳過(guò)了中間步驟(上面代碼片段中的 wordsFound 變量),僅僅是將兩個(gè)函數(shù)調(diào)用一起使用:

var wordsUsed = unique( words( text ) );

注意: 盡管我們通常以從左往右的方式閱讀函數(shù)調(diào)用 ———— 先 unique(..) 然后 words(..) ———— 這里的操作順序?qū)嶋H上是從右往左的,或者說(shuō)是自內(nèi)而外。words(..) 將會(huì)首先運(yùn)行,然后才是 unique(..)。晚點(diǎn)我們會(huì)討論符合我們自然的、從左往右閱讀執(zhí)行順序的模式,叫做 pipe(..)

堆在一起的機(jī)器工作的還不錯(cuò),但有些笨重了,電線掛的到處都是。創(chuàng)造的機(jī)器堆越多,工廠車間就會(huì)變得越凌亂。而且,裝配和維護(hù)這些機(jī)器堆太占用時(shí)間了。

有一天早上,一個(gè)糖果工廠的工程師突然想到了一個(gè)好點(diǎn)子。她想,如果她能在外面做一個(gè)大盒子把所有的電線都藏起來(lái),效果肯定超級(jí)棒;盒子里面,三臺(tái)機(jī)器相互連接,而盒子外面,一切都變得很整潔、干凈。在這個(gè)很贊的機(jī)器的頂部,是傾倒融化巧克力的管道,在它的底部,是吐出包裝好的巧克力糖果的管道。

這樣一個(gè)單個(gè)的組合版機(jī)器,變得更易移動(dòng)和安裝到工廠需要的地方中去了。工廠的車間工人也會(huì)變得更高興,因?yàn)樗麄儾挥迷贁[弄三臺(tái)機(jī)子上的那些按鈕和表盤了;他們很快更喜歡使用這個(gè)獨(dú)立的很贊的機(jī)器。

回到代碼上:我們現(xiàn)在了解到 words(..)unique(..) 執(zhí)行的特定順序 -- 思考:組合的樂(lè)高 -- 是一種我們?cè)趹?yīng)用中其它部分也能夠用到的東西。所以,現(xiàn)在讓我們定義一個(gè)組合這些玩意的函數(shù):

function uniqueWords(str) {
    return unique( words( str ) );
}

uniqueWords(..) 接收一個(gè)字符串并返回一個(gè)數(shù)組。它是 unique(..)words(..) 的組合,并且滿足我們的數(shù)據(jù)流向要求:

wordsUsed <-- unique <-- words <-- text

你現(xiàn)在應(yīng)該能夠明白了:糖果工廠設(shè)計(jì)模式的演變革命就是函數(shù)的組合。

制造機(jī)器

糖果工廠一切運(yùn)轉(zhuǎn)良好,多虧了省下的空間,他們現(xiàn)在有足夠多的地方來(lái)嘗試制作新的糖果了。鑒于之前的成功,管理者迫切的想要發(fā)明新的棒棒的組合版機(jī)器,從而制造越來(lái)越多種類的糖果。

但工廠的工程師們跟不上老板的節(jié)奏,因?yàn)槊看卧煲慌_(tái)新的棒棒的組合版機(jī)器,他們就要花費(fèi)很多的時(shí)間來(lái)造新的外殼,從而適應(yīng)那些獨(dú)立的機(jī)器。

所以工程師們聯(lián)系了一家工業(yè)機(jī)器制供應(yīng)商來(lái)幫他們。他們很驚訝的發(fā)現(xiàn)這家供應(yīng)商竟然提供 機(jī)器制造 器!聽(tīng)起來(lái)好像不可思議,他們買入了一臺(tái)這樣的機(jī)器,這臺(tái)機(jī)器能夠?qū)⒐S中小一點(diǎn)的機(jī)器 ———— 比如負(fù)責(zé)巧克力冷卻、切割的機(jī)器 ———— 自動(dòng)連線,甚至在外面還自動(dòng)包了一個(gè)干凈的大盒子。這么牛的機(jī)器簡(jiǎn)直能把這家糖果工廠送上天了!

回到代碼上,讓我們定義一個(gè)實(shí)用函數(shù)叫做 compose2(..),它能夠自動(dòng)創(chuàng)建兩個(gè)函數(shù)的組合,這和我們手動(dòng)做的是一模一樣的。

function compose2(fn2,fn1) {
    return function composed(origValue){
        return fn2( fn1( origValue ) );
    };
}

// ES6 箭頭函數(shù)形式寫法
var compose2 =
    (fn2,fn1) =>
        origValue =>
            fn2( fn1( origValue ) );

你是否注意到我們定義參數(shù)的順序是 fn2,fn1,不僅如此,參數(shù)中列出的第二個(gè)函數(shù)(也被稱作 fn1)會(huì)首先運(yùn)行,然后才是參數(shù)中的第一個(gè)函數(shù)(fn2)?換句話說(shuō),這些函數(shù)是以從右往左的順序組合的。

這看起來(lái)是種奇怪的實(shí)現(xiàn),但這是有原因的。大部分傳統(tǒng)的 FP 庫(kù)為了順序而將它們的 compose(..) 定義為從右往左的工作,所以我們沿襲了這種慣例。

但是為什么這么做?我認(rèn)為最簡(jiǎn)單的解釋(但不一定符合真實(shí)的歷史)就是我們?cè)谝允謩?dòng)執(zhí)行的書寫順序來(lái)列出它們時(shí),或是與我們從左往右閱讀這個(gè)列表時(shí)看到它們的順序相符合。

unique(words(str)) 以從左往右的順序列出了 unique, words 函數(shù),所以我們讓 compose2(..) 實(shí)用函數(shù)也以這種順序接收它們。現(xiàn)在,更高效的糖果制造機(jī)定義如下:

var uniqueWords = compose2( unique, words );
組合的變體

看起來(lái)貌似 <-- unique <-- words 的組合方式是這兩種函數(shù)能夠被組合起來(lái)的唯一順序。但我們實(shí)際上能夠以另外的目的創(chuàng)建一個(gè)實(shí)用函數(shù),將它們以相反的順序組合起來(lái)。

var letters = compose2( words, unique );

var chars = letters( "How are you Henry?" );
chars;
// ["h","o","w","a","r","e","y","u","n"]

因?yàn)?words(..) 實(shí)用函數(shù),上面的代碼才能正常工作。為了值類型的安全,首先使用 String(..) 將它的輸入強(qiáng)轉(zhuǎn)為一個(gè)字符串。所以 unique(..) 返回的數(shù)組 -- 現(xiàn)在是 words(..) 的輸入 -- 成為了 "H,o,w, ,a,r,e,y,u,n,?" 這樣的字符串。然后 words(..) 中的行為將字符串處理成為 chars 數(shù)組。

不得不承認(rèn),這是個(gè)刻意的例子。但重點(diǎn)是,函數(shù)的組合不總是單向的。有時(shí)候我們將灰方塊放到藍(lán)方塊上,有時(shí)我們又會(huì)將藍(lán)方塊放到最上面。

假如糖果工廠嘗試將包裝好的糖果放入攪拌和冷卻巧克力的機(jī)器,那他們最好要小心點(diǎn)了。

通用組合

如果我們能夠定義兩個(gè)函數(shù)的組合,我們也同樣能夠支持組合任意數(shù)量的函數(shù)。任意數(shù)目函數(shù)的組合的通用可視化數(shù)據(jù)流如下:

finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue

現(xiàn)在糖果工廠擁有了最好的制造機(jī):它能夠接收任意數(shù)量獨(dú)立的小機(jī)器,并吐出一個(gè)大只的、超贊的機(jī)器,能把每一步都按照順序做好。這個(gè)糖果制作流程簡(jiǎn)直棒呆了!簡(jiǎn)直是威利·旺卡(譯者注:《查理和巧克力工廠》中的人物,他擁有一座巧克力工廠)的夢(mèng)想!

我們能夠像這樣實(shí)現(xiàn)一個(gè)通用 compose(..) 實(shí)用函數(shù):

function compose(...fns) {
    return function composed(result){
        // 拷貝一份保存函數(shù)的數(shù)組
        var list = fns.slice();

        while (list.length > 0) {
            // 將最后一個(gè)函數(shù)從列表尾部拿出
            // 并執(zhí)行它
            result = list.pop()( result );
        }

        return result;
    };
}

// ES6 箭頭函數(shù)形式寫法
var compose =
    (...fns) =>
        result => {
            var list = fns.slice();

            while (list.length > 0) {
                // 將最后一個(gè)函數(shù)從列表尾部拿出
                // 并執(zhí)行它
                result = list.pop()( result );
            }

            return result;
        };

現(xiàn)在看一下組合超過(guò)兩個(gè)函數(shù)的例子。回想下我們的 uniqueWords(..) 組合例子,讓我們?cè)黾右粋€(gè) skipShortWords(..)

function skipShortWords(list) {
    var filteredList = [];

    for (let i = 0; i < list.length; i++) {
        if (list[i].length > 4) {
            filteredList.push( list[i] );
        }
    }

    return filteredList;
}

讓我們?cè)俣x一個(gè) biggerWords(..) 來(lái)包含 skipShortWords(..)。我們期望等價(jià)的手工組合方式是 skipShortWords(unique(words(text))),所以讓我們采用 compose(..) 來(lái)實(shí)現(xiàn)它:

var text = "To compose two functions together, pass the 
output of the first function call as the input of the 
second function call.";

var biggerWords = compose( skipShortWords, unique, words );

var wordsUsed = biggerWords( text );

wordsUsed;
// ["compose","functions","together","output","first",
// "function","input","second"]

現(xiàn)在,讓我們回憶一下第 3 章中出現(xiàn)的 partialRight(..) 來(lái)讓組合變的更有趣。我們能夠構(gòu)造一個(gè)由 compose(..) 自身組成的右偏函數(shù)應(yīng)用,通過(guò)提前定義好第二和第三參數(shù)(unique(..)words(..));我們把它稱作 filterWords(..)(如下)。

然后,我們能夠通過(guò)多次調(diào)用 filterWords(..) 來(lái)完成組合,但是每次的第一參數(shù)卻各不相同。

// 注意: 使用 a <= 4 來(lái)檢查,而不是 skipShortWords(..) 中用到的 > 4
function skipLongWords(list) { /* .. */ }

var filterWords = partialRight( compose, unique, words );

var biggerWords = filterWords( skipShortWords );
var shorterWords = filterWords( skipLongWords );

biggerWords( text );
// ["compose","functions","together","output","first",
// "function","input","second"]

shorterWords( text );
// ["to","two","pass","the","of","call","as"]

花些時(shí)間考慮一下基于 compose(..) 的右偏函數(shù)應(yīng)用給了我們什么。它允許我們?cè)诮M合的第一步之前做指定,然后以不同后期步驟 (biggerWords(..) and shorterWords(..)) 的組合來(lái)創(chuàng)建特定的變體。這是函數(shù)式編程中最強(qiáng)大的手段之一。

你也能通過(guò) curry(..) 創(chuàng)建的組合來(lái)替代偏函數(shù)應(yīng)用,但因?yàn)閺挠彝蟮捻樞颍绕鹬皇褂?curry( compose, ..),你可能更想使用 curry( reverseArgs(compose), ..)

注意: 因?yàn)?curry(..)(至少我們?cè)诘?3 章中實(shí)現(xiàn)的是這樣)依賴于探測(cè)參數(shù)數(shù)目(length)或手動(dòng)指定其數(shù)目,而 compose(..) 是一個(gè)可變的函數(shù),所以你需要手動(dòng)指定數(shù)目,就像這樣 curry(.. , 3)

不同的實(shí)現(xiàn)

當(dāng)然,你可能永遠(yuǎn)不會(huì)在生產(chǎn)中使用自己寫的 compose(..),而更傾向于使用某個(gè)庫(kù)所提供的方案。但我發(fā)現(xiàn)了解底層工作的原理實(shí)際上對(duì)強(qiáng)化理解函數(shù)式編程中通用概念非常有用。

所以讓我們看看對(duì)于 compose(..) 的不同實(shí)現(xiàn)方案。我們能看到每一種實(shí)現(xiàn)的優(yōu)缺點(diǎn),特別是性能方面。

我們將稍后在文中查看 reduce(..) 實(shí)用函數(shù)的細(xì)節(jié),但現(xiàn)在,只需了解它將一個(gè)列表(數(shù)組)簡(jiǎn)化為一個(gè)單一的有限值。看起來(lái)像是一個(gè)很棒的循環(huán)體。

舉個(gè)例子,如果在數(shù)字列表 [1,2,3,4,5,6] 上做加法約減,你將要循環(huán)它們,并且隨著循環(huán)將它們加在一起。這一過(guò)程將首先將 12,然后將結(jié)果加 3,然后加 4,等等。最后得到總和:21

原始版本的 compose(..) 使用一個(gè)循環(huán)并且饑渴的(也就是,立刻)執(zhí)行計(jì)算,將一個(gè)調(diào)用的結(jié)果傳遞到下一個(gè)調(diào)用。我們可以通過(guò) reduce(..) (代替循環(huán))做到同樣的事。

function compose(...fns) {
    return function composed(result){
        return fns.reverse().reduce( function reducer(result,fn){
            return fn( result );
        }, result );
    };
}

// ES6 箭頭函數(shù)形式寫法
var compose = (...fns) =>
    result =>
        fns.reverse().reduce(
            (result,fn) =>
                fn( result )
            , result
        );

注意到 reduce(..) 循環(huán)發(fā)生在最后的 composed(..) 運(yùn)行時(shí),并且每一個(gè)中間的 result(..) 將會(huì)在下一次調(diào)用時(shí)作為輸入值傳遞給下一個(gè)迭代。

這種實(shí)現(xiàn)的優(yōu)點(diǎn)就是代碼更簡(jiǎn)練,并且使用了常見(jiàn)的函數(shù)式編程結(jié)構(gòu):reduce(..)。這種實(shí)現(xiàn)方式的性能和原始的 for 循環(huán)版本很相近。

但是,這種實(shí)現(xiàn)局限處在于外層的組合函數(shù)(也就是,組合中的第一個(gè)函數(shù))只能接收一個(gè)參數(shù)。其他大多數(shù)實(shí)現(xiàn)在首次調(diào)用的時(shí)候就把所有參數(shù)傳進(jìn)去了。如果組合中的每一個(gè)函數(shù)都是一元的,這個(gè)方案沒(méi)啥大問(wèn)題。但如果你需要給第一個(gè)調(diào)用傳遞多參數(shù),那么你可能需要不同的實(shí)現(xiàn)方案。

為了修正第一次調(diào)用的單參數(shù)限制,我們可以仍使用 reduce(..) ,但加一個(gè)懶執(zhí)行函數(shù)包裹器:

function compose(...fns) {
    return fns.reverse().reduce( function reducer(fn1,fn2){
        return function composed(...args){
            return fn2( fn1( ...args ) );
        };
    } );
}

// ES6 箭頭函數(shù)形式寫法
var compose =
    (...fns) =>
        fns.reverse().reduce( (fn1,fn2) =>
            (...args) =>
                fn2( fn1( ...args ) )
        );

注意到我們直接返回了 reduce(..) 調(diào)用的結(jié)果,該結(jié)果自身就是個(gè)函數(shù),不是一個(gè)計(jì)算過(guò)的值。函數(shù)讓我們能夠傳入任意數(shù)目的參數(shù),在整個(gè)組合過(guò)程中,將這些參數(shù)傳入到第一個(gè)函數(shù)調(diào)用中,然后依次產(chǎn)出結(jié)果給到后面的調(diào)用。

相較于直接計(jì)算結(jié)果并把它傳入到 reduce(..) 循環(huán)中進(jìn)行處理,這種實(shí)現(xiàn)通過(guò)在組合之前只運(yùn)行 一次 reduce(..) 循環(huán),然后將所有的函數(shù)調(diào)用運(yùn)算全部延遲了 ———— 稱為惰性運(yùn)算。每一個(gè)簡(jiǎn)化后的局部結(jié)果都是一個(gè)包裹層級(jí)更多的函數(shù)。

當(dāng)你調(diào)用最終組合函數(shù)并且提供一個(gè)或多個(gè)參數(shù)的時(shí)候,這個(gè)層層嵌套的大函數(shù)內(nèi)部的所有層級(jí),由內(nèi)而外調(diào)用,以相反的方式連續(xù)執(zhí)行(不是通過(guò)循環(huán))。

這個(gè)版本的性能特征和之前 reduce(..) 基礎(chǔ)實(shí)現(xiàn)版有潛在的差異。在這兒,reduce(..) 只在生成大個(gè)的組合函數(shù)時(shí)運(yùn)行過(guò)一次,然后這個(gè)組合函數(shù)只是簡(jiǎn)單的一層層執(zhí)行它內(nèi)部所嵌套的函數(shù)。在前一版本中,reduce(..) 將在每一次調(diào)用中運(yùn)行。

在考慮哪一種實(shí)現(xiàn)更好時(shí),你的情況可能會(huì)不一樣,但是要記得后面的實(shí)現(xiàn)方式并沒(méi)有像前一種限制只能傳一個(gè)參數(shù)。

我們也能夠使用遞歸來(lái)定義 compose(..)。遞歸式定義的 compose(fn1,fn2, .. fnN) 看起來(lái)會(huì)是這樣:

compose( compose(fn1,fn2, .. fnN-1), fnN );

注意: 我們將在第 9 章揭示更多的細(xì)節(jié),所以如果這塊看起來(lái)讓你疑惑,那么暫時(shí)跳過(guò)該部分是沒(méi)問(wèn)題的,你可以在閱讀完第 9 章后再來(lái)看。

這里是我們用遞歸實(shí)現(xiàn) compose(..) 的代碼:

function compose(...fns) {
    // 拿出最后兩個(gè)參數(shù)
    var [ fn1, fn2, ...rest ] = fns.reverse();

    var composedFn = function composed(...args){
        return fn2( fn1( ...args ) );
    };

    if (rest.length == 0) return composedFn;

    return compose( ...rest.reverse(), composedFn );
}

// ES6 箭頭函數(shù)形式寫法
var compose =
    (...fns) => {
        // 拿出最后兩個(gè)參數(shù)
        var [ fn1, fn2, ...rest ] = fns.reverse();

        var composedFn =
            (...args) =>
                fn2( fn1( ...args ) );

        if (rest.length == 0) return composedFn;

        return compose( ...rest.reverse(), composedFn );
    };

我認(rèn)為遞歸實(shí)現(xiàn)的好處是更加概念化。我個(gè)人覺(jué)得相較于不得不在循環(huán)里跟蹤運(yùn)行結(jié)果,通過(guò)遞歸的方式進(jìn)行重復(fù)的動(dòng)作反而更易懂。所以我更喜歡以這種方式的代碼來(lái)表達(dá)。

其他人可能會(huì)覺(jué)得遞歸的方法在智力上造成的困擾更讓人有些畏懼。我建議你作出自己的評(píng)估。

重排序組合

我們?cè)缙谡劶暗氖菑挠彝箜樞虻臉?biāo)準(zhǔn) compose(..) 實(shí)現(xiàn)。這么做的好處是能夠和手工組合列出參數(shù)(函數(shù))的順序保持一致。

不足之處就是它們排列的順序和它們執(zhí)行的順序是相反的,這將會(huì)造成困擾。同時(shí),不得不使用 partialRight(compose, ..) 提早定義要在組合過(guò)程中 第一個(gè) 執(zhí)行的函數(shù)。

相反的順序,從右往左的組合,有個(gè)常見(jiàn)的名字:pipe(..)。這個(gè)名字據(jù)說(shuō)來(lái)自 Unix/Linux 界,那里大量的程序通過(guò)“管道傳輸”(| 運(yùn)算符)第一個(gè)的輸出到第二個(gè)的輸入,等等(即,ls -la | grep "foo" | less)。

pipe(..)compose(..) 一模一樣,除了它將列表中的函數(shù)從左往右處理。

function pipe(...fns) {
    return function piped(result){
        var list = fns.slice();

        while (list.length > 0) {
            // 從列表中取第一個(gè)函數(shù)并執(zhí)行
            result = list.shift()( result );
        }

        return result;
    };
}

實(shí)際上,我們只需將 compose(..) 的參數(shù)反轉(zhuǎn)就能定義出來(lái)一個(gè) pipe(..)

var pipe = reverseArgs( compose );

非常簡(jiǎn)單!

回憶下之前的通用組合的例子:

var biggerWords = compose( skipShortWords, unique, words );

pipe(..) 的方式來(lái)實(shí)現(xiàn),我們只需要反轉(zhuǎn)參數(shù)的順序:

var biggerWords = pipe( words, unique, skipShortWords );

pipe(..) 的優(yōu)勢(shì)在于它以函數(shù)執(zhí)行的順序排列參數(shù),某些情況下能夠減輕閱讀者的疑惑。pipe(words,unique,skipShortWords) 看起來(lái)和讀起來(lái)會(huì)更簡(jiǎn)單,能知道我們首先執(zhí)行 words(..),然后 unique(..),最后是 skipShortWords(..)

假如你想要部分的應(yīng)用第一個(gè)函數(shù)(們)來(lái)負(fù)責(zé)執(zhí)行,pipe(..) 同樣也很方便。就像我們之前使用 compose(..) 構(gòu)建的右偏函數(shù)應(yīng)用一樣。

對(duì)比:

var filterWords = partialRight( compose, unique, words );

// vs

var filterWords = partial( pipe, words, unique );

你可能會(huì)回想起第 3 章 partialRight(..) 中的定義,它實(shí)際使用了 reverseArgs(..),就像我們的 pipe(..) 現(xiàn)在所做的。所以,不管怎樣,我們得到了同樣的結(jié)果。

在這一特定場(chǎng)景下使用 pipe(..) 的輕微性能優(yōu)勢(shì)在于我們不必再通過(guò)右偏函數(shù)應(yīng)用的方式來(lái)使用 compose(..) 保存從右往左的參數(shù)順序,使用
pipe(..) 我們不必再跟 partialRight(..) 一樣需要將參數(shù)順序反轉(zhuǎn)回去。所以在這里 partial(pipe, ..)partialRight(compose, ..) 要好一點(diǎn)。

一般來(lái)說(shuō),在使用一個(gè)完善的函數(shù)式編程庫(kù)時(shí),pipe(..)compose(..) 沒(méi)有明顯的性能區(qū)別。

抽象

抽象經(jīng)常被定義為對(duì)兩個(gè)或多個(gè)任務(wù)公共部分的剝離。通用部分只定義一次,從而避免重復(fù)。為了展現(xiàn)每個(gè)任務(wù)的特殊部分,通用部分需要被參數(shù)化。

舉個(gè)例子,思考如下(明顯刻意生成的)代碼:

function saveComment(txt) {
    if (txt != "") {
        comments[comments.length] = txt;
    }
}

function trackEvent(evt) {
    if (evt.name !== undefined) {
        events[evt.name] = evt;
    }
}

這兩個(gè)實(shí)用函數(shù)都是將一個(gè)值存入一個(gè)數(shù)據(jù)源,這是通用的部分。不同的是一個(gè)是將值放置到數(shù)組的末尾,另一個(gè)是將值放置到對(duì)象的某個(gè)屬性上。

讓我們抽象一下:

function storeData(store,location,value) {
    store[location] = value;
}

function saveComment(txt) {
    if (txt != "") {
        storeData( comments, comments.length, txt );
    }
}

function trackEvent(evt) {
    if (evt.name !== undefined) {
        storeData( events, evt.name, evt );
    }
}

引用一個(gè)對(duì)象(或數(shù)組,多虧了 JS 中方便的 [] 符號(hào))屬性和將值設(shè)入的通用任務(wù)被抽象到獨(dú)立的 storeData(..) 函數(shù)。這個(gè)函數(shù)當(dāng)前只有一行代碼,該函數(shù)能提出其它多任務(wù)中通用的行為,比如生成唯一的數(shù)字 ID 或?qū)r(shí)間戳存入。

如果我們?cè)诙嗵幹貜?fù)通用的行為,我們將會(huì)面臨改了幾處但忘了改別處的維護(hù)風(fēng)險(xiǎn)。在做這類抽象時(shí),有一個(gè)原則是,通常被稱作 DRY(don"t repeat yourself)。

DRY 力求能在程序的任何任務(wù)中有唯一的定義。代碼不夠 DRY 的另一個(gè)托辭就是程序員們太懶,不想做非必要的工作。

抽象能夠走得更遠(yuǎn)。思考:

function conditionallyStoreData(store,location,value,checkFn) {
    if (checkFn( value, store, location )) {
        store[location] = value;
    }
}

function notEmpty(val) { return val != ""; }

function isUndefined(val) { return val === undefined; }

function isPropUndefined(val,obj,prop) {
    return isUndefined( obj[prop] );
}

function saveComment(txt) {
    conditionallyStoreData( comments, comments.length, txt, notEmpty );
}

function trackEvent(evt) {
    conditionallyStoreData( events, evt.name, evt, isPropUndefined );
}

為了實(shí)現(xiàn) DRY 和避免重復(fù)的 if 語(yǔ)句,我們將條件判斷移動(dòng)到了通用抽象中。我們同樣假設(shè)在程序中其它地方可能會(huì)檢查非空字符串或非 undefined 的值,所以我們也能將這些東西 DRY 出來(lái)。

這些代碼現(xiàn)在變得更 DRY 了,但有些抽象過(guò)度了。開(kāi)發(fā)者需要對(duì)他們程序中每個(gè)部分使用恰當(dāng)?shù)某橄蠹?jí)別保持謹(jǐn)慎,不能太過(guò),也不能不夠。

關(guān)于我們?cè)诒菊轮袑?duì)函數(shù)的組合進(jìn)行的大量討論,看起來(lái)它的好處是實(shí)現(xiàn)這種 DRY 抽象。但讓我們別急著下結(jié)論,因?yàn)槲艺J(rèn)為組合實(shí)際上在我們的代碼中發(fā)揮著更重要的作用。

而且,即使某些東西只出現(xiàn)了一次,組合仍然十分有用 (沒(méi)有重復(fù)的東西可以被抽出來(lái))。

除了通用化和特殊化的對(duì)比,我認(rèn)為抽象有更多有用的定義,正如下面這段引用所說(shuō):

... 抽象是一個(gè)過(guò)程,程序員將一個(gè)名字與潛在的復(fù)雜程序片段關(guān)聯(lián)起來(lái),這樣該名字就能夠被認(rèn)為代表函數(shù)的目的,而不是代表函數(shù)如何實(shí)現(xiàn)的。通過(guò)隱藏?zé)o關(guān)的細(xì)節(jié),抽象降低了概念復(fù)雜度,讓程序員在任意時(shí)間都可以集中注意力在程序內(nèi)容中的可維護(hù)子集上。

《程序設(shè)計(jì)語(yǔ)言》, 邁克爾 L 斯科特

https://books.google.com/book...

// TODO: 給這本書或引用弄一個(gè)更好的參照,至少找到一個(gè)更好的在線鏈接

這段引用表述的觀點(diǎn)是抽象 ———— 通常來(lái)說(shuō),是指把一些代碼片段放到自己的函數(shù)中 ———— 是圍繞著能將兩部分功能分離,從而達(dá)到可以專注于某一獨(dú)立的部分為主要目的來(lái)服務(wù)的。

需要注意的是,這種場(chǎng)景下的抽象并不是為了隱藏細(xì)節(jié),比如把一些東西當(dāng)作黑盒來(lái)對(duì)待。這一觀念其實(shí)更貼近于編程中的封裝性原則。我們不是為了隱藏細(xì)節(jié)而抽象,而是為了通過(guò)分離來(lái)突出關(guān)注點(diǎn)

還記得這段文章的開(kāi)頭,我說(shuō)函數(shù)式編程的目的是為了創(chuàng)造更可讀、更易理解的代碼。一個(gè)有效的方法是將交織纏繞的 ———— 緊緊編織在一起,像一股繩子 ———— 代碼解綁為分離的、更簡(jiǎn)單的 ———— 松散綁定的 ———— 代碼片段。以這種方式來(lái)做的話,代碼的閱讀者將不會(huì)在尋找其它部分細(xì)節(jié)的時(shí)候被其中某塊的細(xì)節(jié)所分心。

我們更高的目標(biāo)是不只對(duì)某些東西實(shí)現(xiàn)一次,這是 DRY 的觀念。實(shí)際上,有些時(shí)候我們確實(shí)在代碼中不斷重復(fù)。于是,我們尋求更分離的實(shí)現(xiàn)方式。我們嘗試突出關(guān)注點(diǎn),因?yàn)檫@能提高可讀性。

另一種描述這個(gè)目標(biāo)的方式就是 ———— 通過(guò)命令式 vs 聲明式的編程風(fēng)格。命令式代碼主要關(guān)心的是描述怎么做來(lái)準(zhǔn)確完成一項(xiàng)任務(wù)。聲明式代碼則是描述輸出應(yīng)該是什么,并將具體實(shí)現(xiàn)交給其它部分。

換句話說(shuō),聲明式代碼從怎么做中抽象出了是什么。盡管普通的聲明式代碼在可讀性上強(qiáng)于命令式,但沒(méi)有程序(除了機(jī)器碼 1 和 0)是完全的聲明式或者命令式代碼。編程者必須在它們之間尋找平衡。

ES6 增加了很多語(yǔ)法功能,能將老的命令式操作轉(zhuǎn)換為新的聲明式形式。可能最清晰的當(dāng)屬解構(gòu)了。解構(gòu)是一種賦值模式,它描述了如何將組合值(對(duì)象、數(shù)組)內(nèi)的構(gòu)成值分解出來(lái)的方法。

這里是一個(gè)數(shù)組解構(gòu)的例子:

function getData() {
    return [1,2,3,4,5];
}

// 命令式
var tmp = getData();
var a = tmp[0];
var b = tmp[3];

// 聲明式
var [ a ,,, b ] = getData();

是什么就是將數(shù)組中的第一個(gè)值賦給 a,然后第四個(gè)值賦給 b怎么做就是得到一個(gè)數(shù)組的引用(tmp)然后手動(dòng)的通過(guò)數(shù)組索引 03,分別賦值給 ab

數(shù)組的解構(gòu)是否隱藏了賦值細(xì)節(jié)?這要看你看待的角度了。我認(rèn)為它知識(shí)簡(jiǎn)單的將是什么怎么做中分離出來(lái)。JS 引擎仍然做了賦值的工作,但它阻止了你自己去抽象怎么做的過(guò)程。

相反的是,你閱讀 [ a ,,, b ] = .. 的時(shí)候,便能看到該賦值模式只不過(guò)是告訴你將要發(fā)生的是什么。數(shù)組的解構(gòu)是聲明式抽象的一個(gè)例子。

將組合當(dāng)作抽象

函數(shù)組合到底做了什么?函數(shù)組合同樣也是一種聲明式抽象。

回想下之前的 shorterWords(..) 例子。讓我們對(duì)比下命令式和聲明式的定義。

// 命令式
function shorterWords(text) {
    return skipLongWords( unique( words( text ) ) );
}

// 聲明式
var shorterWords = compose( skipLongWords, unique, words );

聲明式關(guān)注點(diǎn)在是什么上 -- 這 3 個(gè)函數(shù)傳遞的數(shù)據(jù)從一個(gè)字符串到一系列更短的單詞 -- 并且將怎么做留在了 compose(..)的內(nèi)部。

在一個(gè)更大的層面上看,shorterWords = compose(..) 行解釋了怎么做來(lái)定義一個(gè) shorterWords(..) 實(shí)用函數(shù),這樣在代碼的別處使用時(shí),只需關(guān)注下面這行聲明式的代碼輸出是什么

shorterWords( text );

組合將一步步得到一系列更短的單詞的過(guò)程抽象了出來(lái)。

相反的看,如果我們不使用組合抽象呢?

var wordsFound = words( text );
var uniqueWordsFound = unique( wordsFound );
skipLongWords( uniqueWordsFound );

或者這種:

skipLongWords( unique( words( text ) ) );

這兩個(gè)版本展示的都是一種更加命令式的風(fēng)格,違背了聲明式風(fēng)格優(yōu)先原則。閱讀者關(guān)注這兩個(gè)代碼片段時(shí),會(huì)被更多的要求了解怎么做而不是是什么

函數(shù)組合并不是通過(guò) DRY 的原則來(lái)節(jié)省代碼量。即使 shorterWords(..) 的使用只出現(xiàn)了一次 -- 所以并沒(méi)有重復(fù)問(wèn)題需要避免!-- 從怎么做中分離出是什么仍能幫助我們提升代碼。

組合是一個(gè)抽象的強(qiáng)力工具,它能夠?qū)⒚钍酱a抽象為更可讀的聲明式代碼。

回顧形參

既然我們已經(jīng)把組合都了解了一遍 -- 那么是時(shí)候拋出函數(shù)式編程中很多地方都有用的小技巧了 -- 讓我們通過(guò)在某個(gè)場(chǎng)景下回顧第 3 章的“無(wú)形參”(譯者注:“無(wú)形參”指的是移除對(duì)函數(shù)形參的引用)段落中的 point-free 代碼,并把它重構(gòu)的稍微復(fù)雜點(diǎn)來(lái)觀察這種小技巧。

// 提供該API:ajax( url, data, cb )
var getPerson = partial( ajax, "http://some.api/person" );
var getLastOrder = partial( ajax, "http://some.api/order", { id: -1 } );

getLastOrder( function orderFound(order){
    getPerson( { id: order.personId }, function personFound(person){
        output( person.name );
    } );
} );

我們想要移除的“點(diǎn)”是對(duì) orderperson 參數(shù)的引用。

讓我們嘗試將 person 形參移出 personFound(..) 函數(shù)。要達(dá)到目的,我們需要首先定義:

function extractName(person) {
    return person.name;
}

但據(jù)我們觀察這段操作能夠表達(dá)的更通用些:將任意對(duì)象的任意屬性通過(guò)屬性名提取出來(lái)。讓我們把這個(gè)實(shí)用函數(shù)稱為 prop(..)

function prop(name,obj) {
    return obj[name];
}

// ES6 箭頭函數(shù)形式
var prop =
    (name,obj) =>
        obj[name];

我們處理對(duì)象屬性的時(shí)候,也需要定義下反操作的工具函數(shù):setProp(..),為了將屬性值設(shè)到某個(gè)對(duì)象上。

但是,我們想小心一些,不改動(dòng)現(xiàn)存的對(duì)象,而是創(chuàng)建一個(gè)攜帶變化的復(fù)制對(duì)象,并將它返回出去。這樣處理的原因?qū)⒃诘?5 章中討論更多細(xì)節(jié)。

function setProp(name,obj,val) {
    var o = Object.assign( {}, obj );
    o[name] = val;
    return o;
}

現(xiàn)在,定義一個(gè) extractName(..) ,它能將對(duì)象中的 "name" 屬性拿出來(lái),我們將部分應(yīng)用 prop(..)

var extractName = partial( prop, "name" );

注意: 不要誤解這里的 extractName(..),它其實(shí)什么都還沒(méi)有做。我們只是部分應(yīng)用 prop(..) 來(lái)創(chuàng)建了一個(gè)等待接收包含 "name"屬性的對(duì)象的函數(shù)。我們也能通過(guò)curry(prop)("name")做到一樣的事。

下一步,讓我們縮小關(guān)注點(diǎn),看下例子中嵌套的這塊查找操作的調(diào)用:

getLastOrder( function orderFound(order){
    getPerson( { id: order.personId }, outputPersonName );
} );

我們?cè)撊绾味x outputPersonName(..)?為了方便形象化我們所需要的東西,想一下我們需要的數(shù)據(jù)流是什么樣:

output <-- extractName <-- person

outputPersonName(..) 需要是一個(gè)接收(對(duì)象)值的函數(shù),并將它傳遞給 extractName(..),然后將處理后的值傳給 output(..)

希望你能看出這里需要 compose(..) 操作。所以我們能夠?qū)?outputPersonName(..) 定義為:

var outputPersonName = compose( output, extractName );

我們剛剛創(chuàng)建的 outputPersonName(..) 函數(shù)是提供給 getPerson(..) 的回調(diào)。所以我們還能定義一個(gè)函數(shù)叫做 processPerson(..) 來(lái)處理回調(diào)參數(shù),使用 partialRight(..)

var processPerson = partialRight( getPerson, outputPersonName );

讓我們用新函數(shù)來(lái)重構(gòu)下之前的代碼:

getLastOrder( function orderFound(order){
    processPerson( { id: order.personId } );
} );

唔,進(jìn)展還不錯(cuò)!

但我們需要繼續(xù)移除掉 order 這個(gè)“形參”。下一步是觀察 personId 能夠被 prop(..) 從一個(gè)對(duì)象(比如 order)中提取出來(lái),就像我們?cè)?person 對(duì)象中提取 name 一樣。

var extractPersonId = partial( prop, "personId" );

為了創(chuàng)建傳遞給 processPerson(..) 的對(duì)象( { id: .. } 的形式),讓我們創(chuàng)建一個(gè)實(shí)用函數(shù) makeObjProp(..),用來(lái)以特定的屬性名將值包裝為一個(gè)對(duì)象。

function makeObjProp(name,value) {
    return setProp( name, {}, value );
}

// ES6 箭頭函數(shù)形式
var makeObjProp =
    (name,value) =>
        setProp( name, {}, value );

提示: 這個(gè)實(shí)用函數(shù)在 Ramda 庫(kù)中被稱為 objOf(..)

就像我們之前使用 prop(..) 來(lái)創(chuàng)建 extractName(..),我們將部分應(yīng)用 makeObjProp(..) 來(lái)創(chuàng)建 personData(..) 函數(shù)用來(lái)制作我們的數(shù)據(jù)對(duì)象。

var personData = partial( makeObjProp, "id" );

為了使用 processPerson(..) 來(lái)完成通過(guò) order 值查找一個(gè)人的功能,我們需要的數(shù)據(jù)流如下:

processPerson <-- personData <-- extractPersonId <-- order

所以我們只需要再使用一次 compose(..) 來(lái)定義一個(gè) lookupPerson(..)

var lookupPerson = compose( processPerson, personData, extractPersonId );

然后,就是這樣了!把這整個(gè)例子重新組合起來(lái),不帶任何的“形參”:

var getPerson = partial( ajax, "http://some.api/person" );
var getLastOrder = partial( ajax, "http://some.api/order", { id: -1 } );

var extractName = partial( prop, "name" );
var outputPersonName = compose( output, extractName );
var processPerson = partialRight( getPerson, outputPersonName );
var personData = partial( makeObjProp, "id" );
var extractPersonId = partial( prop, "personId" );
var lookupPerson = compose( processPerson, personData, extractPersonId );

getLastOrder( lookupPerson );

哇哦。沒(méi)有形參。并且 compose(..) 在兩處地方看起來(lái)相當(dāng)有用!

我認(rèn)為在這樣的場(chǎng)景下,即使推導(dǎo)出我們最終答案的步驟有些多,但最終的代碼卻變得更加可讀,因?yàn)槲覀儾挥迷偃ピ敿?xì)的調(diào)用每一步了。

即使你不想看到或命名這么多中間步驟,你依然可以通過(guò)不使用獨(dú)立變量而是將表達(dá)式串起來(lái)來(lái)來(lái)保留無(wú)點(diǎn)特性。

partial( ajax, "http://some.api/order", { id: -1 } )
(
    compose(
        partialRight(
            partial( ajax, "http://some.api/person" ),
            compose( output, partial( prop, "name" ) )
        ),
        partial( makeObjProp, "id" ),
        partial( prop, "personId" )
    )
);

這段代碼肯定沒(méi)那么羅嗦了,但我認(rèn)為比之前的每個(gè)操作都有其對(duì)應(yīng)的變量相比,可讀性略有降低。但是不管怎樣,組合幫助我們實(shí)現(xiàn)了無(wú)點(diǎn)的風(fēng)格。

總結(jié)

函數(shù)組合是一種定義函數(shù)的模式,它能將一個(gè)函數(shù)調(diào)用的輸出路由到另一個(gè)函數(shù)的調(diào)用上,然后一直進(jìn)行下去。

因?yàn)?JS 函數(shù)只能返回單個(gè)值,這個(gè)模式本質(zhì)上要求所有組合中的函數(shù)(可能第一個(gè)調(diào)用的函數(shù)除外)是一元的,當(dāng)前函數(shù)從上一個(gè)函數(shù)輸出中只接收一個(gè)輸入。

相較于在我們的代碼里詳細(xì)列出每個(gè)調(diào)用,函數(shù)組合使用 compose(..) 實(shí)用函數(shù)來(lái)提取出實(shí)現(xiàn)細(xì)節(jié),讓代碼變得更可讀,讓我們更關(guān)注組合完成的是什么,而不是它具體做什么

組合 ———— 聲明式數(shù)據(jù)流 ———— 是支撐函數(shù)式編程其他特性的最重要的工具之一。

【上一章】翻譯連載 | JavaScript 輕量級(jí)函數(shù)式編程-第3章:管理函數(shù)的輸入 |《你不知道的JS》姊妹篇

【下一章】翻譯連載 | JavaScript輕量級(jí)函數(shù)式編程-第5章:減少副作用 |《你不知道的JS》姊妹篇

iKcamp原創(chuàng)新書《移動(dòng)Web前端高效開(kāi)發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開(kāi)售。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/85185.html

相關(guān)文章

  • 翻譯連載 |《你不知道JS妹篇 |《JavaScript 量級(jí)函數(shù)編程》- 引言&前言

    摘要:我稱之為輕量級(jí)函數(shù)式編程。序眾所周知,我是一個(gè)函數(shù)式編程迷。函數(shù)式編程有很多種定義。本書是你開(kāi)啟函數(shù)式編程旅途的絕佳起點(diǎn)。事實(shí)上,已經(jīng)有很多從頭到尾正確的方式介紹函數(shù)式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、...

    2bdenny 評(píng)論0 收藏0
  • 翻譯連載 |《你不知道JS妹篇 |《JavaScript 量級(jí)函數(shù)編程》- 1

    摘要:所以我覺(jué)得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過(guò)了深入的研究和審查,并且可以被驗(yàn)證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會(huì)認(rèn)為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液...

    omgdog 評(píng)論0 收藏0
  • 翻譯連載 | 9 :遞歸(上)-《JavaScript量級(jí)函數(shù)編程》 |《你不知道JS

    摘要:一旦我們滿足了基本條件值為,我們將不再調(diào)用遞歸函數(shù),只是有效地執(zhí)行了。遞歸深諳函數(shù)式編程之精髓,最被廣泛引證的原因是,在調(diào)用棧中,遞歸把大部分顯式狀態(tài)跟蹤換為了隱式狀態(tài)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;...

    MasonEast 評(píng)論0 收藏0
  • 翻譯連載 |《你不知道JS妹篇 |《JavaScript 量級(jí)函數(shù)編程》- 2 :函

    摘要:從某些方面來(lái)講,這章回顧的函數(shù)知識(shí)并不是針對(duì)函數(shù)式編程者,非函數(shù)式編程者同樣需要了解。什么是函數(shù)針對(duì)函數(shù)式編程,很自然而然的我會(huì)想到從函數(shù)開(kāi)始。如果你計(jì)劃使用函數(shù)式編程,你應(yīng)該盡可能多地使用函數(shù),而不是程序。指的是一個(gè)函數(shù)聲明的形參數(shù)量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:...

    Riddler 評(píng)論0 收藏0
  • 全本 | iKcamp翻譯 | 《JavaScript 量級(jí)函數(shù)編程》|《你不知道JS妹篇

    摘要:本書主要探索函數(shù)式編程的核心思想。我們?cè)谥袘?yīng)用的僅僅是一套基本的函數(shù)式編程概念的子集。我稱之為輕量級(jí)函數(shù)式編程。通常來(lái)說(shuō),關(guān)于函數(shù)式編程的書籍都熱衷于拓展閱讀者的知識(shí)面,并企圖覆蓋更多的知識(shí)點(diǎn)。,本書統(tǒng)稱為函數(shù)式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后)...

    paney129 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<