JS進階篇--JS中的反柯里化( uncurrying)

反柯里化

相反,反柯里化的做用在與擴大函數的適用性,使原本做爲特定對象所擁有的功能的函數能夠被任意對象所用.
即把以下給定的函數簽名,javascript

obj.func(arg1, arg2)

轉化成一個函數形式,簽名以下:html

func(obj, arg1, arg2)

這就是 反柯里化的形式化描述。java

例如,下面的一個簡單實現:jquery

Function.prototype.uncurrying = function() {
    var that = this;
    return function() {
        return Function.prototype.call.apply(that, arguments);
    }
};

function sayHi () {
    return "Hello " + this.value +" "+[].slice.call(arguments);
}
var sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:'world'},"hahaha"));

解釋:編程

  • uncurrying是定義在Function的prototype上的方法,所以對全部的函數均可以使用此方法。調用時候:sayHiuncurrying=sayHi.uncurrying(),因此uncurrying中的 this 指向的是 sayHi 函數; (通常原型方法中的 this 不是指向原型對象prototype,而是指向調用對象,在這裏調用對象是另外一個函數,在javascript中函數也是對象)
  • call.apply(that, arguments) 把 that 設置爲 call 方法的上下文,而後將 arguments 傳給 call方法,前文的例子,that 實際指向 sayHi,因此調用 sayHiuncurrying(arg1, arg2, ...) 至關於 sayHi.call(arg1, arg2, ...);
  • sayHi.call(arg1, arg2, ...), call 函數把 arg1 當作 sayHi的上下文,而後把 arg2,... 等剩下的參數傳給sayHi,所以最後至關於 arg1.sayHi(arg2,...);
  • 所以,這至關於 sayHiuncurrying(obj,args) 等於 obj.sayHi(args)。

最後,咱們反過來看,其實反柯里化至關於把原來 sayHi(args) 的形式,轉換成了 sayHiuncurrying(obj,args),使得sayHi的使用範圍泛化了。 更抽象地表達, uncurryinging反柯里化,使得原來 x.y(z) 調用,能夠轉成 y(x',z) 形式的調用 。 假設x' 爲x或者其餘對象,這就擴大了函數的使用範圍。數組

通用反柯里化函數

上面例子中把uncurrying寫進了prototype,這不太好,咱們其實能夠把 uncurrying 單獨封裝成一個函數;app

var uncurrying= function (fn) {
    return function () {
        var args=[].slice.call(arguments,1);
        return fn.apply(arguments[0],args);        
    }    
};

上面這個函數很清晰直接。
使用時 調用 uncurrying 並傳入一個現有函數 fn, 反柯里化函數會返回一個新函數,該新函數接受的第一個實參將綁定爲 fn 中 this的上下文,其餘參數將傳遞給 fn 做爲參數。函數式編程

因此,對反柯里化更通俗的解釋能夠是 函數的借用,是函數可以接受處理其餘對象,經過借用泛化、擴大了函數的使用範圍。函數

因此 uncurrying更常見的用法是對 Javascript 內置的其餘方法的 借調 而不用本身都去實現一遍。this

文字描述比較繞,仍是繼續看代碼:

var test="a,b,c";
console.log(test.split(","));

var split=uncurrying(String.prototype.split);   //[ 'a', 'b', 'c' ]
console.log(split(test,','));                   //[ 'a', 'b', 'c' ]

split=uncurrying(String.prototype.split) 給 uncurrying 傳入一個具體的fn,即String.prototype.split ,split 函數就具備了 String.prototype.split 的功能,函數調用 split(test,',') 時,傳入的第一個參數爲 split 執行的上下文,剩下的參數至關於傳給原 String.prototype.split 函數。

再看一個例子:

var $ = {};
console.log($.push);                          // undefined
var pushUncurrying = uncurrying(Array.prototype.push);
$.push = function (obj) {
    pushUncurrying(this,obj);
};
$.push('first');
console.log($.length);                        // 1
console.log($[0]);                            // first
console.log($.hasOwnProperty('length'));      // true

這裏模仿了一個「相似jquery庫」 實現時借用 Array 的 push 方法。 咱們知道對象是沒有 push 方法的,因此 console.log(obj.push) 返回 undefined,能夠借用Array 來處理 push,由原生的數組方法(js引擎)來維護 僞數組對象的 length 屬性和數組成員。

一樣的道理,咱們還能夠繼續有:

var indexof=uncurrying(Array.prototype.indexOf);
$.indexOf = function (obj) {
    return indexof(this,obj);
};
$.push("second");
console.log($.indexOf('first'));              // 0
console.log($.indexOf('second'));             // 1
console.log($.indexOf('third'));              // -1

例如咱們在實現本身的類庫時,有些方法若是有些方法和原生的相似,那麼能夠經過 uncurrying 借用原生方法。

咱們還能夠把 Function.prototype.call/apply 方法 uncurring,例如:

var call= uncurrying(Function.prototype.call);
var fn= function (str) {
    console.log(this.value+str);
};
var obj={value:"Foo "};
call(fn, obj,"Bar!");                       // Foo Bar!

這樣能夠很是靈活地把函數也當作一個普通「數據」來使用,有函數式編程的趕腳,在一些類庫中常常能看到這樣的用法。

通用 uncurrying 函數的進擊

上面的 uncurrying 函數是比較符合思惟習慣容易理解的版本,接下來一路進擊,看幾個其餘版本:

首先,若是B格高一點,uncurrying 也可能寫成這樣:

var uncurrying= function (fn) {
    return function () {
        var context=[].shift.call(arguments);
        return fn.apply(context,arguments);
    }
};

固然若是還須要再提高B格,那麼還能夠是這樣:

var uncurrying= function (fn) {
    return function () {        
        return Function.prototype.call.apply(fn,arguments);
    }
};

參考地址

淺析 JavaScript 中的 函數 uncurrying 反柯里化

相關文章
相關標籤/搜索