XDM,JS如何函數式編程?看這就夠了!(二)

承接上一篇《XDM,JS如何函數式編程?看這就夠了!(一)》,咱們知道了函數式編程的幾個基本概念。ajax

這裏做簡要回顧編程

  1. 函數式編程目的是爲了數據流更加明顯,從而代碼更具可讀性;
  2. 函數須要一個或多個輸入(理想狀況下只需一個!)和一個輸出,輸入輸出是顯式的代碼將更好閱讀;
  3. 閉包是高階函數的基礎;
  4. 警戒匿名函數;
  5. 棄用 this 指向;

本篇將着重介紹第 2 點中函數的輸入,它是 JS 輕量函數式編程的基礎之基礎,重要之重要!!!api

偏函數

傳參現狀

咱們常常會寫出這樣的代碼:數組

function ajax(url,data,callback) {
    // ..
}

function getPerson(data,cb) {
    ajax( "http://some.api/person", data, cb );
}

複製代碼

ajax 函數有三個入參,在 getPerson 函數裏調用,其中 url 已肯定,data 和 cb 兩個參數則等待傳入。(由於不少時候參數都不是在當前能肯定的,須要等待其它函數的操做後肯定了再繼續傳入)markdown

可是咱們的原則是:入參最理想的狀況下只需一個!閉包

怎樣優化,能夠實現這一點呢?函數式編程

咱們或許能夠在外層再套一個函數來進一步肯定傳參,好比:函數

function getCurrentUser(cb) {
    
    ...// 經過某些操做拿到 CURRENT_USER_ID

    getPerson( { user: CURRENT_USER_ID }, cb );
}
複製代碼

這樣,data 參數也已經肯定,cb 參數仍等待傳入;函數 getCurrentUser 就只有一個入參了!post

數據的傳遞路線是:優化

ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
複製代碼

這樣函數參數個數逐漸減小的過程就是偏應用

也能夠說:getCurrentUser(cb)getOrder(data,cb) 的偏函數,getOrder(data,cb)ajax(url,data,cb) 函數的偏函數

設想下:

若是一個函數是這樣的:

function receiveMultiParam(a,b,c,......,x,y,z){
    // ..
}
複製代碼

咱們難道還要像上面那樣手動指定外層函數進行逐層嵌套嗎?

顯示咱們不會這麼作!

封裝 partial

咱們只須要封裝一個 partial(..) 函數:

function partial(fn,...presetArgs) {
    return function partiallyApplied(...laterArgs){
        return fn( ...presetArgs, ...laterArgs );
    };
}
複製代碼

它的基礎邏輯是:

var partial =
    (fn, ...presetArgs) =>
        (...laterArgs) =>
            fn( ...presetArgs, ...laterArgs );
複製代碼

把函數做爲入參!還記得咱們以前所說:

一個函數若是能夠接受或返回一個甚至多個函數,它被叫作高階函數。

咱們借用 partial() 來實現上述舉例:

var getPerson = partial( ajax, "http://some.api/person" );

var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
複製代碼

如下函數內部分析很是重要:

運行機制

getPerson() 的內部運行機制是:

var getPerson = function partiallyApplied(...laterArgs) {
    return ajax( "http://some.api/person", ...laterArgs );
};
複製代碼

getCurrentUser() 的內部運行機制是:

var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
    var getPerson = function innerPartiallyApplied(...innerLaterArgs){
        return ajax( "http://some.api/person", ...innerLaterArgs );
    };

    return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs );
}
複製代碼

數據進行了傳遞:

getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
複製代碼

咱們經過這樣一層額外的函數包裝層,實現了更增強大的數據傳遞,

咱們將須要減小參數輸入的函數傳入 partial()中做爲第一個參數,剩下的是 presetArgs,當前已知幾個,就能夠寫幾個。還有不肯定的入參 laterArgs,能夠在肯定後繼續追加。

像這樣進行額外的高階函數包裝層,是函數式編程的精髓所在!

「隨着本系列的繼續深刻,咱們將會把許多函數互相包裝起來。記住,這就是函數式編程!」 —— 《JavaScript 輕量級函數式編程》

實際上,實現 getCurrentUser() 還能夠這樣寫:

// 版本 2
var getCurrentUser = partial(
    ajax,
    "http://some.api/person",
    { user: CURRENT_USER_ID }
);

// 內部實現機制

var getCurrentUser = function partiallyApplied(...laterArgs) {
    return ajax(
        "http://some.api/person",
        { user: CURRENT_USER_ID },
        ...laterArgs
    );
};
複製代碼

可是版本 1 由於重用了已經定義好的函數,因此它在表達上更清晰一些。它被認爲更加貼合函數式編程精神!

拓展 partial

咱們再看看 partial() 函數還可它用:

function partial(fn,...presetArgs) {
    return function partiallyApplied(...laterArgs){
        return fn( ...presetArgs, ...laterArgs );
    };
}
複製代碼

好比:將數組 [1,2,3,4,5] 每項都加 3,一般咱們會這麼作:

function add(x,y) {
    return x + y

[1,2,3,4,5].map( function adder(val){
    return add( 3, val );
} );

// [4,5,6,7,8]
複製代碼

藉助 partial()

[1,2,3,4,5].map( partial( add, 3 ) );

// [4,5,6,7,8]
複製代碼

add(..) 不能直接傳入 map(..) 函數裏,經過偏應用進行處理後則能傳入;

實際上,partial() 函數還能夠有不少變體:

回想咱們以前調用 Ajax 函數的方式:ajax( url, data, cb )。若是要偏應用 cb 而稍後再指定 data 和 url 參數,咱們應該怎麼作呢?

function reverseArgs(fn) {
    return function argsReversed(...args){
        return fn( ...args.reverse() );
    };
}

function partialRight( fn, ...presetArgs ) {
    return reverseArgs(
        partial( reverseArgs( fn ), ...presetArgs.reverse() )
    );
}

var cacheResult = partialRight( ajax, function onResult(obj){
    cache[obj.id] = obj;
});

// 處理後:
cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } );
複製代碼

柯里化

函數柯里化其實是一種特殊的偏函數。

咱們用 curry(..) 函數來實現此前的 ajax(..) 例子,它會是這樣的:

var curriedAjax = curry( ajax );

var personFetcher = curriedAjax( "http://some.api/person" );

var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } );

getCurrentUser( function foundUser(user){ /* .. */ } );
複製代碼

柯里化函數:接收單一實參(實參個數:1)並返回另外一個接收下一個實參的函數。

它將一個函數從可調用的 f(a, b, c) 轉換爲可調用的 f(a)(b)(c)。

實現:

function curry(fn,arity = fn.length) {
    return (function nextCurried(prevArgs){
        return function curried(nextArg){
            var args = prevArgs.concat( [nextArg] );

            if (args.length >= arity) {
                return fn( ...args );
            }
            else {
                return nextCurried( args );
            }
        };
    })( [] );
}
複製代碼

階段小結

咱們爲何要如此着重去談「偏函數」(partial(sum,1,2)(3))或「柯里化」(sum(1)(2)(3))呢?

第一,是顯而易見的,偏函數或柯里化,能夠將「指定分離實參」的時機和地方獨立開來;

第二,更有重要意義的是,當函數只有一個形參時,咱們可以比較容易地組合它們。這種單元函數,便於進行後續的組合函數;

對函數進行包裝,使其成爲一個高階函數是函數式編程的精髓!

至此,有了「偏函數」這門武器大炮,咱們將逐漸轟開 JS 輕量級函數式編程的面紗 ~

以上。

我是掘金安東尼,公衆號【掘金安東尼】,點贊👍關注👀,輸出暴露輸入!持續追蹤!

相關文章
相關標籤/搜索