JavaScript之函數柯里化

什麼是柯里化(currying)?

維基百科中的解釋是:柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。意思就是當函數被調用時,返回的函數還須要設置一些傳入的參數。app

首先來看一個簡單的例子,有下面一個函數:函數

function add(num1, num2) {
  return num1 + num2;
}

咱們把它改寫成下面這樣:性能

var fn = function(a) {
  return function (b) {
    return a + b;
  }
}

能夠這樣調用函數:fn(2)(3)。上面使用了匿名函數來實現多參數函數的方法,雖然這並非柯里化的函數,但能夠幫助咱們理解柯里化的含義。this

實現通用柯里化函數

咱們能夠在內置構造函數Function()的原型上來添加一個柯里化函數,這樣全部的函數均可以調用。下面是通用柯里化函數的實現:prototype

Function.prototype.currying = function () {
  var that = this;
  var args = [].slice.call(arguments);
  return function () {
    return that.apply(null, args.concat([].slice.call(arguments)));
  }
}

如今用柯里化函數將上面的add函數柯里化:code

var curriedAdd = add.currying(2);
curriedAdd(3); // 5

也能夠一次性傳入兩個參數:對象

var curriedAdd = add.currying(2, 3);
curriedAdd(); // 5

咱們知道在原生對象的原型上擴展方法是不太好的,由於可能會致使命名衝突。因此最好不要把currying函數擴展在Function的原型上,下面是改寫的currying函數:原型

function currying(fn) {
  var args = [].slice.call(arguments, 1);
  return function () {
    return that.apply(null, args.concat([].slice.call(arguments)));
  }
}

改寫以後currying函數的第一個參數是要被柯里化的函數,能夠這樣調用:it

var curriedAdd = currying(add, 2);
curriedAdd(3); // 5
或
var curriedAdd = currying(add, 2, 3);
curriedAdd(); // 5

上面的add函數只是兩個數字的相加,若是咱們須要n個數字相加,上面的currying函數已經不能知足要求了,下面是修改後的currying函數:io

function currying(fn) {
  var argsArr = [];
  return function () {
    if (arguments.length === 0) {
      return fn.apply(null, argsArr);
    } else {
      [].push.apply(argsArr, arguments);
    }
  }
}

多個數字相加:

var add = function () {
  var num = 0;
  [].forEach.call(arguments, function (item, i) {
    num += item;
  })
  return num;   
}

var curriedAdd = currying(add);
curriedAdd(2);
curriedAdd(3);
curriedAdd(4);
curriedAdd(5);
curriedAdd(); // 14

這樣作有什麼好處呢?假如說咱們只想知道這個月花了多少錢,而中間的某一天以前花了多少咱們並不想知道,咱們只在意結果,不在意過程,上面的currying函數很好地解決了這個問題。有的人說這樣作能夠節省性能,我倒以爲這和性能沒多大關係,或者說這樣作的目的並非爲了性能,由於每次計算結果和最後一塊兒計算結果是同樣的,都是要計算同樣的次數。還有一個好處就是能夠複用currying函數,好比咱們要多個數字相乘或者其餘操做,均可以用currying函數,處理數字只需修改fn參數就能夠。

說到柯里化就不得不說Function.prototype.bind這個方法了,它也實現了函數的柯里化。咱們能夠本身來實現一個bind函數:

function bind(fn, context) {
  var args = [].slice.call(arguments, 2);
  return function () {
    return fn.apply(context, args.concat([].slice.call(arguments)));
  }
}

假如咱們須要改變fn中的this上下文,就能夠用bind函數,不然能夠用currying函數。

相關文章
相關標籤/搜索