js高階函數應用—函數柯里化和反柯里化(二)

上一篇文章中咱們介紹了函數柯里化,順帶提到了偏函數,接下來咱們繼續話題,進入今天的主題—函數的反柯里化。java

在上一篇文章中柯里化函數你可能須要去敲許多代碼,理解不少代碼邏輯,不過這一節咱們討論的反科裏化你可能不須要看不少代碼邏輯,主要是理解反柯里化的核心思想,其實這種思想可能在你剛入門js時候就接觸到了,並且你幾乎每天在寫代碼過程當中使用它。編程

首先須要理解反柯里化咱們先來回顧上一節的內容,科裏化簡單歸納一下就是咱們作了這樣一件事情:把接受多個參數的函數變換成接受一個單一參數的函數,而且返回新的函數來執行餘下的數,公式表示基本模式爲fn(a,b,c,d)=>fn(a)(b)(c)(d)。數組

那麼很容易類比過來,反柯里化就是fn(a)(b)(c)(d)=>fn(a,b,c,d)是這樣嗎?其實就是這樣,不要懷疑就是這麼簡單,只不過實現過程沒有這麼直接明瞭而已,反柯里化大概是作了這麼一件事情:把已經內置的特定使用場景的函數經過參數解放出來,提升函數的適用範圍。數據結構

轉化爲公式:app

curyyA=fn(a);編程語言

curryA(b)(c)(d)=>fn(a,b,c,d);函數

或者ui

curyyAb=fn(a)(b);//或者curyyAb=fn(a,b);this

curryAb(c)(d)=>fn(a,b,c,d);spa

......以此類推

爲了方便理解咱們把上一節中的curry函數add版本拿過來

const curry = (fn, ...arg) => {
    let all = arg || [],
        length = fn.length;
    return (...rest) => {
        let _args = all.slice(0);
        _args.push(...rest);
        if (_args.length < length) {
            return curry.call(this, fn, ..._args);
        } else {
            return fn.apply(this, _args);
        }
    }
}
const add = curry((a, b) => a + b);
const add6 = add(6);
console.log(add6(1)); //7
console.log(add6(2)); //8

能夠看到咱們柯里化後的函數,每次執行了後返回的函數對於應用場景針對性越強,例如這個add6就是任意一個數和6的和,比起原來的add能夠實現任意兩數的和,add6應用範圍更窄了,不過add6這個應用場景更有針對性,例如咱們就須要一個任意數與6的和的時候這個add6就適應咱們的場景。

這樣應該很容易理解了吧,若是還有問題咱們來看一個更簡單的例子:

let getTag = (type) => {
    return `小明是一個${type}`
}
getTag('好學生');//小明是一個好學生
getTag('好老師');//小明是一個好老師

這個getTag函數根據傳入的參數返回小明的類型「小明是一個xxx」,不過這個函數是咱們在專門獲得小明的類型用的,若是咱們如今有個需求須要的到「小華是一個xxx」,你總不會再寫一個函數

 getTagHua = (type) => `小華是一個${type}`

來獲得「小華是一個xxx」的結果吧,就算剛入門編程語言時候咱們也不會這麼寫;要實現這個需求很簡單,再傳入一個參數就好了

let getTag2 = (name, type) => `${name}是一個${type}`

getTag2('小華', '好學生')//小華是個好學生

到這裏咱們能夠對比一下getTag和getTag2這兩個函數,咱們發現:

1.getTag是一個getTag2柯里化後帶着一個「小明」的內置參數的版本

2.getTag只針對獲得小明類型的場景使用

3.getTag2是一個getTag泛化後的版本,適用範圍更廣

能夠理解爲getTag2是getTag的反柯里化函數,是否是很簡單,反柯里化的函數編程思想咱們每天在用,只是沒注意到而已。如今再回去看以前的公式

curyyA=fn(a);

curryA(b)(c)(d)=>fn(a,b,c,d);

這種類型的轉化是否是就很容易理解了。

接下來咱們接續咱們的話題,咱們如今知道了,反柯里化是一種編程思想,經過解析出函數內部限定條件,而後把限定條件當作參數傳給函數,從而提升函數使用範圍的一種編程思想,既然這樣咱們就很容易理解咱們下面這種狀況了

class Book {
    constructor(name) {
        this.name = name;
    }
    static declare() {
        console.log('A Study Method');
    }
    sayName() {
        console.log(this.name);
    }
}

class Blog {
    constructor(name) {
        this.name = name;
    }
}
let book = new Book('javacript語言精粹');
let blog = new Blog('博客園');

book.sayName();                     //javacript語言精粹  
Book.prototype.sayName.call(blog); //博客園
Book.declare.call(blog);           //A Study Method

如上面咱們在開發常常遇到的,blog想用book類的方法,用call和apply改變一下this指向就好了,咱們經常使用的call,apply函數自己就是反科裏化思想的體現,把函數的調用對象當作參數傳給函數,從而提升函數的適用範圍,相似於作了這麼一件事情:

obj.fn(a,b)=>fn(object,a,b) 的轉化

經過轉化使得fn再也不只適用於obj調用還可讓其餘的object調用,提升其適用範圍

那麼咱們把這個轉化過程用函數實現一下

Function.prototype.uncurrying = function() { //外邊這個不要寫成箭頭函數,由於咱們具體反柯里化什麼函數是咱們調用時候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

let sayName = Book.prototype.sayName.uncurrying();
let deClare = Book.declare.uncurrying();
sayName(blog) //博客園
deClare(blog) //A Study Method
deClare(book) //A Study Method

能夠看到咱們把Book類才能調用的靜態方法declare,和book實例的sayName反柯里化後,這種只針對類名調用的方法和只針對Book類對象調用的方法可讓其餘對象調用了。

固然咱們也能夠把一些js內置對象的方法uncurrying一下,好比一個字符串‘sdjkfjksfsdkslkdjsdf’,咱們想把它每個字符都拆出來放到一個數組中,或者每一項都拼個固定的字符再返回一數組,咱們能夠吧Array裏的map方法uncurrying一下:

let map = Array.prototype.map.uncurrying();
console.log(map('yuweryiweryuie', val => val + 'test'))

固然不少js內置對象的方法能夠uncurrying的,這裏不作過多介紹,由於都是咱們經常使用的call,apply的場景,即一些具備相似數據結構或者相同迭代器的對象咱們一般會借用其餘的對象方法

咱們這裏還給出一種上述uncurrying的實現方式:

Function.prototype.uncurrying = function() {
    return (...rest) => Function.prototype.call.apply(this, rest);
}

若是你不是很理解代碼也不要緊,沒太大影響,由於這個實現只是把以前的

Function.prototype.uncurrying = function() { //外邊這個不要寫成箭頭函數,由於咱們具體反柯里化什麼函數是咱們調用時候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

這個函數的傳obj的這個工做交給call來完成了,若是你仍是不理解建議去mdn上看看call和apply用法,理一下邏輯就行,這裏不作過多闡釋,好了咱們currying和uncurrying的內容就到這了。

相關文章
相關標籤/搜索