美文网首页前端笔记
一道题看透函数柯里化

一道题看透函数柯里化

作者: faremax | 来源:发表于2017-10-02 20:19 被阅读8次

    对于函数的柯里化(currying)应该不陌生,简单来说 Currying 技术是一种通过把多个参数填充到函数体中,实现将函数转换为一个新的经过简化的(使之接受的参数更少)函数的技术。当发现正在调用同一个函数时,并且传递的参数绝大多数都是相同的,那么用一个Curry化的函数是一个很好的选择.

    下面利用闭包实现一个curry化的加法函数, 我们简单理解一下 curry 化:

    function add(x, y){
        if(x && y) return x + y;
        if(!x && !y) throw Error("Cannot calculate");
        return function(newx){
            return x + newx;
        };
    }
    add(3)(4); //7
    add(3, 4); //7
    var newAdd = add(5);
    newAdd(8); //13
    var add2000 = add(2000);
    add2000(100); //2100
    

    这样做其实很类似 bind:

    function add(a, b){
      console.log(a+b);
      return a + b;
    }
    
    add(3, 4);   //7
    add.bind(null, 3)(4);   //7
    var newAdd = add.bind(null, 5);
    newAdd(8);   //13
    var add2000 = add.bind(null, 2000);
    add2000(100); //2100
    

    同理也可以使用 call 和 apply, 因为他们可以实现 bind 的功能:

    Function.prototype.bind = function(context){
      var _this = this;
      var args = [].slice.call(arguments, 1);
    
      return function (){
        innerArgs = [].slice.call(arguments);
        if(innerArgs && innerArgs.length > 0)
          args.push.apply(args, innerArgs);
        return _this.apply(context, args);
      }
    }
    add(3, 4);   //7
    add.bind(null, 3)(4);   //7
    var newAdd = add.bind(null, 5);
    newAdd(8);   //13
    var add2000 = add.bind(null, 2000);
    add2000(100); //2100
    

    但是,如果看到了这个题:

    实现一个函数sum,运算结果可以满足如下预期结果:

    sum(1,2,3);       //6
    sum(2,3)(2);      //7
    sum(1)(2)(3)(4);  //10
    sum(2)(4,1)(2);   //9
    

    还觉得简单么?我们理一下思路。首先试想一下这个 sum 函数的结构:

    function sum(){
      return function(){
        return function(){
          //...
        }
      }
    }
    

    这个函数返回的一定是个函数,但貌似需要写无限个,这个不合理,我们修改一下:

    function sum(){
      function innerSum(){
        //...
        return innerSum();
      }
      return innerSum();
    }
    

    这样一来每次调用就不需要定义无限个函数了。我们完善里面的代码:

    //sum(1,2,3);       //6
    //sum(2,3)(2);      //7
    //sum(1)(2)(3)(4);  //10
    //sum(2)(4,1)(2);   //9
    function sum(){
      var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
      function innerSum(){
        var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
        cur += next;
        return innerSum;
      }
      return innerSum;
    }
    

    这样 sum 函数的柯里化过程就完成了,但是这个函数的返回的总是一个函数,这样我们如何输出数值呢?我们可以借助隐式类型转换需要的 toString 函数实现:

    function sum(){
      var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
      function innerSum(){
        var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
        cur += next;
        return innerSum;
      }
      innerSum.toString = function(){
        return cur;
      }
      return innerSum;
    }
    console.log(sum(1,2,3));       //6
    console.log(sum(2,3)(2));      //7
    console.log(sum(1)(2)(3)(4));  //10
    console.log(sum(2)(4,1)(2));   //9
    

    计算结果没错,我们还可以换作 valueOf 实现:

    function sum(){
      var cur = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
      function innerSum(){
        var next = [].slice.call(arguments).reduce(function(a,b){return a+b;},0);
        cur += next;
        return innerSum;
      }
      innerSum.valueOf = function(){
        return cur;
      }
      return innerSum;
    }
    console.log(sum(1,2,3));       //6
    console.log(sum(2,3)(2));      //7
    console.log(sum(1)(2)(3)(4));  //10
    console.log(sum(2)(4,1)(2));   //9
    

    其实,如果同时存在 toString 和 valueOf 系统会先调用 toString, 然后调用valueOf,返回值自然是 valueOf 的返回值。这个很基础,这里就不提了。

    通用柯里化方法

    通用的柯里化写法其实比之前的 sum 函数要简单许多

    var currying = function(fn) {
      // 主要还是收集所有需要的参数到一个数组中,便于统一计算
      var args = [].slice.call(arguments, 1);
      return function(){
          var _args = args.concat([].slice.call(arguments));
          return fn.apply(null, _args);
      }
    }
    
    var sum = function(){
      var args = [].slice.call(arguments);
      return args.reduce(function(a, b) {
          return a + b;
      });
    };
    
    var sum10 = currying(sum, 10);
    console.log(sum10(20, 10));  // 40
    console.log(sum10(10, 5));   // 25
    

    相关文章

      网友评论

        本文标题:一道题看透函数柯里化

        本文链接:https://www.haomeiwen.com/subject/yjbtyxtx.html