当前位置 主页 > 网站技术 > 代码类 >

    javascript实现函数柯里化与反柯里化过程解析

    栏目:代码类 时间:2019-10-14 15:06

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感;下面来一起看看究竟什么是函数柯里化:

    维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术。其由数学家Haskell Brooks Curry提出,并以curry命名。

    概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学习闭包知识点再来学习本篇博文https://www.jb51.net/article/171398.htm)来进行返回一个函数,内部函数接收除开第一个参数外的其余参数进行操作并输出,这个就是函数的柯里化;

    举个小例子:

    场景(需求):

    众所周知程序员每天加班的时间还是比较多的,如果我们需要计算一个程序员每天的加班时间,那么我们的第一反应应该是这样;

    var overtime=0;
    function time(x){
      return overtime+=x;
    }
    
    time(1); //1
    time(2); //3
    time(3); //6

    上面的代码固然没有问题,可是需要每天调用都算加一下当天的时间,很麻烦,并且每调用一次函数都要进行一定的操作,如果数据量巨大,有可能会有影响性能的风险,那么有没有可以偷懒又能解决问题的办法呢?有的!

    function time(x){
     return function(y){
        return x+y;
      }   
    }
    
    var times=time(0);
    times(3);

    但是上面代码依然存在问题,在实际开发中很多时候我们的参数是不确定的,上面代码虽然简单的实现了柯里化的基本操作,但是对于参数不确定的情况是处理不了的;所以存在着函数参数的局限性;不过我们从上面的代码中基本可以知道函数柯里化是个啥意思了;就是一个函数调用的时候只允许传入一个参数,然后通过闭包返回内部函数去处理和接收剩余参数,返回的函数通过闭包的方式记住了time的第一个参数;

    我们再来把代码改造一下:

    // 首先定义一个变量接收函数
    var overtime = (function() {
    //定义一个数组用来接收参数
     var args = [];
    //这里运用闭包,调用外部函数返回一个内部函数
     return function() {
      //arguments是浏览器内置对象,专门用来接收参数
      //如果参数的长度为0即没有参数的时候
      if(arguments.length === 0) {
        //定义变量用来累加
       var time = 0;
        //循环累加,用i和args的长度进行比较
       for (var i = 0, l = args.length; i < l; i++) {
        //进行累加操作  等价于time=time+args[i]
        time += args[i];
       }
        // 返回累加的结果
       return time;
        //如果arguments对象参数长度不为零,即有参数的时候
      }else {
        //定义的空数组添加arguments参数作为数组项,第一个参数古args作为改变this指向,第二个参数arguments把剩余参数作为数组形式添加至空数组中
       [].push.apply(args, arguments);
      }
     }
    })();
    
    overtime(3.5);  // 第一天
    overtime(4.5);  // 第二天
    overtime(2.1);  // 第三天
    //...
    
    console.log( overtime() );  // 10.1

    代码经过我们的改造已经实现了功能,但是这不是一个函数柯里化的完整实现,那么我们要怎么完整实现呢?下面我们来介绍一种通用的实现方式:

    通用的实现方式:

    //定义方法currying,先传入一个参数
    var currying=function(fn){
      //定义空数组装arguments对象的剩余参数
     var args=[];
      //利用闭包返回一个函数处理剩余参数
     return function (){
        //如果arguments的参数长度为0,即没有剩余参数
      if(arguments.length===0){
        //执行上面方法
        //这里的this指向下面的s,类似于s(),代表参数长度为0的时候直接调用函数
       return fn.apply(this,args)
      }
      console.log(arguments)
      //如果arguments的参数长度不为0,即还有剩余参数
      //在数组的原型对象上添加数组,apply用来更改this的指向为args
      //将[].slice.call(arguments)的数组添加到原型数组上
      //这里的[].slice.call(arguments)===Array.prototype.slice.call(arguments)实质上就是将arguments对象转成数组并具有slice功能
    
      Array.prototype.push.apply(args,[].slice.call(arguments))
      //args.push([].slice.call(arguments))
      console.log(args)
      //这里返回的arguments.callee是返回的闭包函数,callee是arguments对象里面的一个属性,用于返回正被执行的function对象
      return arguments.callee
     }
    }
      //这里调用currying方法并传入add函数,结果会返回闭包内部函数
     var s=currying(add);
      //调用闭包内部函数,当有参数的时候会将参数逐步添加到args数组中,待没有参数传入的时候直接调用
      //调用的时候支持链式操作
     s(1)(2)(3)();
    //也可以一次性传入多个参数
      s(1,2,3);
     console.log(s());