javascript之反柯里化(uncurrying)

在JavaScript中,當咱們調用對象的某個方法時,其實不用去關心該對象本來是否被設計爲擁有這個方法,這是動態類型語言的特色。能夠經過反柯里化(uncurrying)函數實現,讓一個對象去借用一個本來不屬於他的方法。

一般讓對象去借用一個本來不屬於它的方法,能夠用call和apply實現,以下javascript

var obj1 = {
    name:'sven'
}
var obj2 = {
    getName:function(){
        return this.name
    }
}
console.log(obj2.getName.call(obj1))//sven

更常見的場景之一是讓類數組對象去借用Array.prototype的方法;html

(function(){
    Array.prototype.push.call(arguments,4)
    console.log(arguments);//[1, 2, 3, 4]
})(1,2,3)

擴展:爲何類數組對象可以借用數組的方法呢?不妨理解下V8的引擎源碼,就以Array.prototype.push爲例:java

function ArrayPush() {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
  var array = TO_OBJECT(this);
  var n = TO_LENGTH_OR_UINT32(array.length);
  var m = %_ArgumentsLength();
  .......
  for (var i = 0; i < m; i++) {
    array[i+n] = %_Arguments(i);
  }
  var new_length = n + m;
  array.length = new_length;
  return new_length;
}

經過這段代碼大體能夠看出,Array.prototype.push其實是一個屬性複製的過程,把參數按照下標依次添加到被push的對象上面,順便修改了這個對象的length屬性,這個對象究竟是數組仍是類數組並不重要。從源碼能夠看出,只要對象自己能夠存取屬性,且length屬性可讀寫,就能夠借用Array原型的push方法。node

 

這樣一來,方法中用到this的地方,就不在侷限本來的對象,而是加以泛化並獲得更廣的適用性。那麼有沒有辦法把泛化this的過程提取出來呢?那麼反柯里化(uncurrying)就是解決這個問題的。反科裏化(uncurrying)的話題來自JavaScript之父Brendan Eich在2011年發表的一篇文章,如下代碼是實現方式之一:設計模式

Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments);
  };
};

而後就能夠定義一個push函數,更加簡潔和明瞭的實現了一個不在侷限於數組的push方法。以下:數組

var push = Array.prototype.push.uncurrying();
(function(){
    push(arguments,4);
    console.log(arguments);//[1,2,3,4]
})(1,2,3)

除了剛剛的一種反柯里化實現,還有另外一種實現方式:app

Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    return Function.prototype.call.apply(self,arguments)
  };
}
看似實現很簡單,但理解就有點費力,再次作下闡釋:
按照上邊的push反柯里化函數Array.prototype.push.uncurrying()分析:
1.self = Array.prototype.push函數;
2.apply中的arguments = [目標數組,參數1,參數2]//實例中就等同於[[1,2,3],4]
對函數原型call方法執行apply方法做用至關於
Function.prototype.call.apply(Array.prototype.push,[[1,2,3],4])
再參考下邊call和apply類源碼實現進行分步理解
Function.prototype.apply= function(context,array) {
  // this=Function.prototype.call; context=Array.prototype.push; array=[[1,2,3],4]
context.fn
= this;
  //Array.prototype.push.fn = Function.prototype.call.bind(Array.prototype.push) 第一步
var args = []; for(var i = 0, len =array.length; i < len; i++) { args.push('arguments[' + i + ']'); }
eval(
'context.fn(' + args +')');
  // Array.prototype.push.fn([1,2,3],4)=== Function.prototype.call.bind(Array.prototype.push)([1,2,3],4) 第二步
  
delete context.fn; }
Function.prototype.call = function(context) {//[1,2,3],4

  //由第二步bind可知this爲Array.prototype.push context=[1,2,3] arguments=[[1,2,4],4]
context.fn
= this;
  //[1,2,3].fn=Array.prototype.push.bind([1,2,3]) 第三步
var args = [];
  //0爲上下文,因此此處參數遍歷應從1開始 for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); }
eval(
'context.fn(' + args +')');
  //[1,2,3].fn(4) === Array.prototype.push.bind([1,2,3])(4) 第四步
delete context.fn; }
Array.prototype.push.bind([1,2,3])(4)===Array.prototype.push.call([1,2,3],4)

 

 https://92node.com/article/js-uncurrying.html函數

參考書籍: javascript設計模式與開發實踐
相關文章
相關標籤/搜索