函数柯里化

一、概念

函数柯里化:可以将接收多个参数的函数转化为接收单一参数的函数,而且能返回接收余下参数且返回结果的新函数(只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数)数组

二、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.

相关文章
相关标签/搜索