柯里化

1、什么是柯里化

  javascript忍者中说:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。

  柯里化又称部分求值,字面意思就是不会立刻求值,而是到了需要的时候再去求值。

  其含义是给函数分步传递参数,每次传递参数后部分应用参数,  

  并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。

 


 

2、举个🌰 

  我们有这样一个场景,记录程序员一个月的加班总时间,

  那么好,我们首先要做的是记录程序员每天加班的时间,

  然后把一个月中每天的加班的时间相加,就得到了一个月的加班总时间。

 

  但问题来了,我们有很多种方法可以实现它,比如最简单的:

var monthTime = 0;

function overtime(time) {
 return monthTime += time;
}

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log(monthTime);    // 10.1

   

  每次传入加班时间都进行累加,这样当然没问题,

  但你知道,如果数据量很大的情况下,这样会大大牺牲性能。

  

  那怎么办?这就是柯里化要解决的问题。

 

  其实我们不必每天都计算加班时间,只需要保存好每天的加班时间,

  在月底时计算这个月总共的加班时间,所以,其实只需要在月底计算一次就行。

  

  下面的overtime函数还不是一个柯里化函数的完整实现,但可以帮助我们了解其核心思想:

var overtime = (function() {
  var args = [];

  return function() {
    if(arguments.length === 0) {
      var time = 0;
      for (var i = 0, l = args.length; i < l; i++) {
        time += args[i];
      }
      return time;
    }else {
      [].push.apply(args, arguments);
    }
  }
})();

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log( overtime() );    // 10.1

 


 
3、再举个🌰
  
  有一个厨师,要做饭,但是餐馆的人没有把菜买齐,
  这样,需要打杂的买菜,放在厨师厨房,
  再买料酒,放在厨师厨房,等买齐了,叫厨师过来,
  好了,原料齐了,可以做饭了。
  
  这个时候,厨师利用原料,把饭做好。
  厨师就像一个函数,他有自己的功能(做饭),
  但是参数(原料)不齐,每次执行这个函数,在参数不齐的情况下,
  只能返回一个新的函数,这个新的函数已经内置了之前的参数,当参数齐了之后完成他本身的功能。
  例如一个最简单的加法函数:
//函数定义
function add(x,y){
    return x + y;
}
//函数调用
add(3,4);//5

 

  如果采用柯里化是怎样将接受两个参数的函数变成接受单一参数的函数呢,其实很简单如下:

//函数表达式定义
var add = function(x){
    return function(y){
        return x + y;
    }
};
//函数调用
add(3)(4);

  其实实质利用的就是闭包的概念

 

  要实现一个这样的加法函数,使得:

add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() === 42

 

  首先,可以看到,这个函数,只有当参数为空的时候,才执行之前所有数值的加法,

  这样的嵌套可以无限进行,当有参数的时候,add(1,2,3),

  这个时候的返回值应该是一个函数,

  这个函数存储了1,2,3但是没有执行加法(执行了也行,此处假设就不执行,只是起到保存参数的作用),

  这样,继续执行add(1,2,3)(2)()就能输出1+2+3+2=8

  要实现这样的一个函数,首先,返回值在参数不为空的时候必定返回一个函数,
  该函数还保存了之前的参数,这就需要用到闭包。
  最终的实现如下:
// add 函数柯里化
function add(){
    //建立args,利用闭包特性,不断保存arguments
    var args = [].slice.call(arguments);
       //方法一,新建_add函数实现柯里化
    var _add = function(){
        if(arguments.length === 0){
            //参数为空,对args执行加法
            return args.reduce(function(a,b){return a+b});
        }else {
            //否则,保存参数到args,返回一个函数
            [].push.apply(args,arguments);
            return _add;
        }
    }
    //返回_add函数
    return _add;
    
    // //方法二,使用arguments.callee实现柯里化
    // return function () {
  //       if (arguments.length === 0) {
  //           return args.reduce(function(a,b){return a+b});
  //       }
  //       Array.prototype.push.apply(args, arguments);
  //       return arguments.callee;
  //   }
}
console.log(add(1,2,3)(1)(2)(3)(4,5,6)(7,8)());//42

 

  

实现的原理主要是:

  1. 闭包保存args变量,存储之前的参数
  2. 新建一个_add函数,参数的长度为0,就执行加法,否则,存储参数到args,然后返回函数自身(可以选择匿名函数,返回arguments.callee即可,意思相同,见代码中注释掉的部分,但是在严格模式下不能使用,所以还是使用方法一比较稳妥)。
//  通用的函数柯里化构造方法
function curry(func){
    //新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
    var args = [].slice.call(arguments,1);
    //新建_func函数作为返回值
    var _func =  function(){
        //参数长度为0,执行func函数,完成该函数的功能
        if(arguments.length === 0){
            return func.apply(this,args);
        }else {
            //否则,存储参数到闭包中,返回本函数
            [].push.apply(args,arguments);
            return _func;
        }
    }
    return _func;
}

function add(){
    return [].reduce.call(arguments,function(a,b){return a+b});
}
console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)(5,6,6,7,8,8)(1)(1)(1)());//69

 


 

 

4、柯里化作用

  • 事件绑定的时候检测应该用dom几的方式,柯里化的话,只需要检测一次,返回一个新的函数来进行事件绑定(我觉得所有需要判断的地方,如果判断条件在一次访问中不改变的话,都可以写成柯里化的形式,从而达到只进行一次判断的效果)
  • 利用柯里化的思想,可以自己写一个bind函数
  • 延迟执行某个函数
 
以上。
posted on 2018-12-10 17:55  薛小白  阅读(702)  评论(0编辑  收藏  举报