JavaScript函式柯里化詳解

admin發表於2018-08-06

本章節詳細介紹一下如何實現函式柯里化,需要的朋友可以做一下參考。

關於函式柯里化的作用可以參閱JavaScript函式柯里化的作用一章節。

百科上關於函式柯里化的定義如下:

[HTML] 純文字檢視 複製程式碼
在電腦科學中,柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術。

上面的概念雖然已經闡明柯里化的概念,但是對於初學者來說可能會有一點困擾,下面就通過程式碼例項介紹一下什麼是函式的柯里化,並且它是如何實現的。函式柯里化的呼叫外在表現如下:

[JavaScript] 純文字檢視 複製程式碼
add(1)(2)(3)(4)

原本的呼叫方式如下:

[JavaScript] 純文字檢視 複製程式碼
add(1,2,3,4)

通過上面的兩段程式碼,是不是可以對函式柯里化的理解增加了一點。

看下面一個最為原始的實現方式:

[JavaScript] 純文字檢視 複製程式碼
var uncurried = function (a, b, c) {
  //code
};
 
var curried = function (a) {
  return function (b) {
    return function (c) {
      //code
    };
  };
};

第二個函式就是最為粗野的一種柯里化實現方式,如果巢狀的函式很多的話,那複雜程度是無法想象的。

下面就介紹一下如何實現一個通用的柯里化函式,先看一個程式碼例項:

[JavaScript] 純文字檢視 複製程式碼
function sub_curry(fn /*, 其他引數 */) {
  var args = [].slice.call(arguments, 1);
  return function () {
    return fn.apply(this, args.concat(toArray(arguments)));
  };
}

下面簡單對上面的程式碼做一下介紹:

(1).fn是要被柯里化的函式。

(2)./*, 其他引數 */,要被傳遞的引數,比如fn(a,b,c),這個引數可以是"a,b,c"或者"a,b"等。

(3).var args = [].slice.call(arguments, 1),這個是將sub_curry的除去fn外的引數轉換為一個陣列。

(4).return function () {

  return fn.apply(this, args.concat(toArray(arguments)));

},返回一個函式,此函式可以接受新的引數,這些新引數可以和args合併,也就是應該傳遞給fn的引數。

使用方式如下:

[JavaScript] 純文字檢視 複製程式碼
var fn = function (a, b, c) { return [a, b, c]; };
fn("a", "b", "c");
sub_curry(fn, "a")("b", "c");
sub_curry(fn, "a", "b")("c");
sub_curry(fn, "a", "b", "c")();

上面程式碼的幾種使用方式都是等價的,已經很有函式柯里化的感覺了(在很多教程中函式柯里化就到此為止),但是還是不夠完美,因為上面的方式只有()()這麼兩級呼叫,再多就不好用了,下面就給出一個比較完美的函式柯里化程式碼:

[JavaScript] 純文字檢視 複製程式碼執行程式碼
function toArray(elements) {
  var core_slice = Array.prototype.slice;
  return core_slice.call(elements);
}
 
function sub_curry(fn /*, 其他引數 */) {
  var args = [].slice.call(arguments, 1);
  return function () {
    return fn.apply(this, args.concat(toArray(arguments)));
  };
}
 
function curry(fn, length) {
  length = length || fn.length;
  return function () {
    if (arguments.length < length) {
      var combined = [fn].concat(toArray(arguments));
      return curry(sub_curry.apply(this, combined), length - arguments.length);
    }
    else {
      return fn.apply(this, arguments);
    }
  };
}
function func(a, b, c) {
  var num;
  num = a + b + c;
  return num;
}
var curriedFunc = curry(func);
console.log(func(1,2,3));
console.log(curriedFunc(1)(2)(3));

上面的程式碼比較完美的實現了函式柯里化功能,下面介紹一下它的實現過程。

一.程式碼註釋:

(1).function toArray(elements) {},此函式可以將集合轉換為陣列。

(2).var core_slice = Array.prototype.slice,將Array圓形物件中的slice方法的引用賦值給變數core_slice

(3).return core_slice.call(elements),將集合轉換為陣列,並返回。

(4).function sub_curry(){},此函式在上面已經介紹了,這裡就不做說明。

(5).function curry(fn, length) {},此函式實現了函式柯里化功能,第一個引數是要被柯里化的函式,第二個引數規定要傳遞幾次引數,也就是(para)的個數,比如如果length是2,那麼舉要func(1)(2)這樣呼叫兩次,可以人為傳遞也可以通過fn.length獲取函式形參的數目,當然看具體的使用場景。

總體來說,curry()函式就是返回一個新的函式,此新函式能夠在傳遞的引數沒有達到length規定的個數時,繼續遞迴呼叫curry()函式,並且返回的所有新函式的引數最終能夠累積連線起來,最後傳遞給fn函式。

(6).length = length || fn.length,如果傳遞了length,就是用length,否則獲取fn函式的形參。

(7).return function () {},返回柯里化後的函式,可以遞迴呼叫curry()函式,用來實現引數的累積。

(8).if (arguments.length < length) {},判斷傳遞的引數是否小於length,如果不小於的話,那麼說明已經是最後一個()呼叫了,那麼就執行return fn.apply(this, arguments),否則的話,就需要遞迴柯里化,繼續累積引數。

(9).var combined = [fn].concat(toArray(arguments)),將函式fn和傳遞的引數生成一個陣列。

(10).return curry(sub_curry.apply(this, combined), length - arguments.length),進行遞迴柯里化操作。

二.相關閱讀:

(1).slice()可以參閱javascript Array slice()一章節。

(2).prototype可以參閱javascript prototype原型一章節。

(3).apply()可以參閱javascript apply()一章節。

(4).concat()可以參閱javascript Array concat()一章節。

(5).arguments可以參閱javascript arguments一章節。

相關文章