函數柯里化

一、概念

函數柯里化:可以將接收多個參數的函數轉化爲接收單一參數的函數,而且能返回接收餘下參數且返回結果的新函數(只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數)數組

二、add函數引入

 1 // 普通的add函數
 2 function add(x, y) {
 3     return x + y
 4 }
 5 
 6 // Currying後
 7 function curryingAdd(x) {
 8     return function (y) {
 9         return x + y
10     }
11 }
12 
13 add(1, 2)           // 3
14 curryingAdd(1)(2)   // 3

  其實是將add函數的兩個參數x、y轉換成先用一個函數接收參數x,而且返回一個函數去處理餘下的參數y。閉包

進一步app

// 實現一個求和函數構造器,使得當該函數被鏈式調用n次後,返回求和的值
function curryingSum (n) {
    var result = 0;
  function callback (num) {
    n--;
    result += num;
    if(n > 0) {
      return callback;
    } else {
      return result;
    }
  }
  return callback;
}

// useage
var add = curryingSum(4)
var result = add(1)(2)(3)(4);
console.log(result); // 10

// or
var add = curryingSum(6)
add = add(1)
add = add(2)(3)
add = add(4)(5)(6)
console.log(result); // 21

思考:若是不提供終止條件n,函數應該怎麼實現?????函數

三、柯里化的通用實現

通用版優化

function curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var newArgs = args.concat(Array.prototype.slice.call(arguments));
    return fn.apply(this, newArgs)
  }
}

curry函數的第一個參數是要動態建立柯里化的函數,餘下的參數存儲在args變量中。this

執行 curry 函數返回的函數接收新的參數與 args 變量存儲的參數合併,並把合併的參數傳入給柯里化了的函數。spa

function add(a, b, c) {
    return a + b + c;
}
var multi = curry(add);
multi(2,3,4);     //9

可是以上代碼沒法實現add(2)(3)(4)prototype

優化版code

function curry(fn, args) {
  var _args = args || [], len = fn.length;
  return function() {
    // 如下語句不可取
    // _args = _args.concat(Array.prototype.slice.call(arguments))
    var newArgs = _args.concat(Array.prototype.slice.call(arguments));
    if(newArgs.length < len) {
      return curry.call(this, fn, newArgs);
    } else {
      return fn.apply(this, newArgs);
    }
  }
}

function add(a, b, c) {
  return a + b + c;
}
var curryAdd = curry(add);
curryAdd(1,2,3)       //6
curryAdd(1,2)(3)      //6
curryAdd(1)(2,3)      //6
curryAdd(1)(2)(3)     //6

 

以上實現必須事先知道參數的個數,爲了讓代碼更靈活,達到隨意傳參的效果又該怎麼作呢???對應了以上的思考blog

擴展版

function add() {
    // 第一次執行時,定義一個數組專門用來存儲全部的參數
    var _args = Array.prototype.slice.call(arguments);

    // 在內部聲明一個函數,利用閉包的特性保存_args並收集全部的參數值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
 }

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

 

四、curry好處

1)參數複用

// 正常正則驗證字符串 reg.test(txt)

// 函數封裝後
function match(reg, txt) {
  return reg.test(txt)
}

//curry以後
function curryMatch(reg) {
  return function(txt) {
    return reg.test(txt)
  }
}

var hasNumber = curryMatch(/\d+/g)
var hasLetter = curryMatch(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

通常能夠直接調用match方法進行正則校驗;可是可能不少地方用到校驗數字的地方,此時能夠複用reg參數生成hasNumber函數,直接調用hasNumber即可以

2)提早確認

var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

//換一種寫法可能比較好理解一點,上面就是把isSupport這個參數給先肯定下來了
var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

第一種寫法也是比較常見,可是第二種寫法相對一第一種寫法就是自執行而後返回一個新的函數,這樣其實就是提早肯定了會走哪個方法,避免每次都進行判斷

3)延遲執行

Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}

像咱們js中常常使用的bind,實現的機制就是Currying.

相關文章
相關標籤/搜索