一道題看透函數柯里化

對於函數的柯里化(currying)應該不陌生,簡單來講 Currying 技術是一種經過把多個參數填充到函數體中,實現將函數轉換爲一個新的通過簡化的(使之接受的參數更少)函數的技術。當發現正在調用同一個函數時,而且傳遞的參數絕大多數都是相同的,那麼用一個Curry化的函數是一個很好的選擇.數組

下面利用閉包實現一個curry化的加法函數, 咱們簡單理解一下 curry 化:閉包

function add(x, y){
    if(x && y) return x + y;
    if(!x && !y) throw Error("Cannot calculate");
    return function(newx){
        return x + newx;
    };
}
add(3)(4); //7
add(3, 4); //7
var newAdd = add(5);
newAdd(8); //13
var add2000 = add(2000);
add2000(100); //2100

這樣作其實很相似 bind:app

function add(a, b){
  console.log(a+b);
  return a + b;
}

add(3, 4);   //7
add.bind(null, 3)(4);   //7
var newAdd = add.bind(null, 5);
newAdd(8);   //13
var add2000 = add.bind(null, 2000);
add2000(100); //2100

同理也可使用 call 和 apply, 由於他們能夠實現 bind 的功能:函數

Function.prototype.bind = function(context){
  var _this = this;
  var args = [].slice.call(arguments, 1);

  return function (){
    innerArgs = [].slice.call(arguments);
    if(innerArgs && innerArgs.length > 0)
      args.push.apply(args, innerArgs);
    return _this.apply(context, args);
  }
}
add(3, 4);   //7
add.bind(null, 3)(4);   //7
var newAdd = add.bind(null, 5);
newAdd(8);   //13
var add2000 = add.bind(null, 2000);
add2000(100); //2100

可是,若是看到了這個題:this

實現一個函數sum,運算結果能夠知足以下預期結果:
sum(1,2,3);       //6
sum(2,3)(2);      //7
sum(1)(2)(3)(4);  //10
sum(2)(4,1)(2);   //9

還以爲簡單麼?咱們理一下思路。首先試想一下這個 sum 函數的結構:prototype

function sum(){
  return function(){
    return function(){
      //...
    }
  }
}

這個函數返回的必定是個函數,但貌似須要寫無限個,這個不合理,咱們修改一下:code

function sum(){
  function innerSum(){
    //...
    return innerSum();
  }
  return innerSum();
}

這樣一來每次調用就不須要定義無限個函數了。咱們完善裏面的代碼:io

//sum(1,2,3);       //6
//sum(2,3)(2);      //7
//sum(1)(2)(3)(4);  //10
//sum(2)(4,1)(2);   //9
function sum(){
  var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
  function innerSum(){
    var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
    cur += next;
    return innerSum;
  }
  return innerSum;
}

這樣 sum 函數的柯里化過程就完成了,可是這個函數的返回的老是一個函數,這樣咱們如何輸出數值呢?咱們能夠藉助隱式類型轉換須要的 toString 函數實現:console

function sum(){
  var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
  function innerSum(){
    var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
    cur += next;
    return innerSum;
  }
  innerSum.toString = function(){
    return cur;
  }
  return innerSum;
}
console.log(sum(1,2,3));       //6
console.log(sum(2,3)(2));      //7
console.log(sum(1)(2)(3)(4));  //10
console.log(sum(2)(4,1)(2));   //9

計算結果沒錯,咱們還能夠換做 valueOf 實現:function

function sum(){
  var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
  function innerSum(){
    var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
    cur += next;
    return innerSum;
  }
  innerSum.valueOf = function(){
    return cur;
  }
  return innerSum;
}
console.log(sum(1,2,3));       //6
console.log(sum(2,3)(2));      //7
console.log(sum(1)(2)(3)(4));  //10
console.log(sum(2)(4,1)(2));   //9

其實,若是同時存在 toString 和 valueOf 系統會先調用 toString, 而後調用valueOf,返回值天然是 valueOf 的返回值。這個很基礎,這裏就不提了。

通用柯里化方法

通用的柯里化寫法其實比以前的 sum 函數要簡單許多

var currying = function(fn) {
  // 主要仍是收集全部須要的參數到一個數組中,便於統一計算
  var args = [].slice.call(arguments, 1);
  return function(){
      var _args = args.concat([].slice.call(arguments));
      return fn.apply(null, _args);
  }
}

var sum = function(){
  var args = [].slice.call(arguments);
  return args.reduce(function(a, b) {
      return a + b;
  });
};

var sum10 = currying(sum, 10);
console.log(sum10(20, 10));  // 40
console.log(sum10(10, 5));   // 25
相關文章
相關標籤/搜索