美文网首页
【JS】高阶函数与函数柯里化

【JS】高阶函数与函数柯里化

作者: 匿于烟火中 | 来源:发表于2020-12-31 14:54 被阅读0次

    高阶函数

    至少满足以下条件的函数:

    柯里化(Currying)

    什么是柯里化

    柯里化是函数式编程中的一种进阶技巧。

    柯里化的直接表现形式就是,当我们有一个函数f(a,b,c),通过柯里化转换,使得这个函数可以被这样调用f(a)(b)(c)。

    柯里化有什么用途?

    • 参数可以复用,便于封装语法糖
      例如:系统中经常有基础的log方法,可以记录不同时间的程序运行信息,记录具体的日志信息。我们假设这个log方法调用一次,就向后台更新一条log记录。
    function log(date, importance, message) {
     $.post(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
    }
    

    比如我们现在要记录当天的日志信息:

    log(new Date(), "DEBUG", "some debug"); 
    log(new Date(), "DEBUG2", "some debug3"); 
    

    我们会发现其实第一个参数是不变的,只有后面的其他参数是变化的。
    有了currying,我们可以这样去调用:

    let curryLog = curry(log);
    let logNow = curryLog(new Date());
    logNow("DEBUG2", "some debug3");
    

    就减少了很多冗余代码。语义也更加清晰。

    一步一步实现Currying function

    当我们定义一个普通的函数,分别传入参数,我们可以得到参数相加的结果。

    function sum(a,b){
      return a+b;
    }
    sum(1,2);//3
    

    现在我们希望可以把sum方法转换一下,可以这样被调用:

    sum(1,2);//3
    sum(1)(2);//3
    

    初始实现思路

    前置知识:【JS】函数参数arguments与rest参数解析

    • arguments来保存参数
      因为传入的参数数量是不确定的,因此我们第一个思路就是借助JS当中的arguments对象来实现。
    • 如果传入的参数的个数小于被柯里化的函数定义的形式参数的个数,那需要把传入的参数保留下来,并且要返回函数可以继续接收下一个参数。

    递归实现法

    function sum(a, b,c) {
      return a + b + c;
    }
    function curried(fn) {
      var slice = Array.prototype.slice;
      var outer = slice.call(arguments,1);
      return function(){
        var inner = slice.call(arguments);
        var args = outer.concat(inner);
        console.log('args',args);
        return fn.apply(this,args);
      }
    }
    function curry(fn,length) {
      var slice = Array.prototype.slice;
      let len = length | fn.length;
      return function () {
        // arguments.length是传入的实参的数量
        //fn.length是函数形参的数量
        if (arguments.length < len) {
          // 如果被柯里化后的函数调用时,实参的数量少于fn形参的数量
          var combined = [fn].concat(slice.call(arguments));
          // 就需要把参数保存下来,并且能够继续返回一个函数,接收后续的参数
          console.log('combined', combined);
          var sub = curried.apply(this, combined);
          console.log('curried', sub);
          return curry(sub, len - arguments.length);
        } else {
          return fn.apply(this,arguments);
        }
      }
    }
    var currySum = curry(sum);
    console.log(currySum(1)(2)(3));
    // output:
    //combined (2) [ƒ, 1]
    //curried ƒ (){
    //     var inner = slice.call(arguments);
    //     var args = outer.concat(inner);
    //     console.log('args',args);
    //     return fn.apply(this,args);
    //   }
    // combined (2) [ƒ, 2]
    // curried ƒ (){
    //     var inner = slice.call(arguments);
    //     var args = outer.concat(inner);
    //     console.log('args',args);
    //     return fn.apply(this,args);
    //   }
    //  args (2) [2, 3]
    //  args (3) [1, 2, 3]
    //  6
    

    从console出的结果我们可以看出来,每次调用的参数都被保存起来了。
    curry函数中,length参数作为递归结束的条件,每保存一个参数,就把长度相应减少。
    curried函数用来返回嵌套的函数,通过组合内层和外层的参数,把参数保留起来。
    当递归结束的时候,arguments里面就包含了所有的参数。
    JavaScript专题之函数柯里化
    js-info_Currying

    循环实现法

    function curry(fn, args) {
        var len = fn.length;
        args = args || [];
        return function() {
          var _args = args.slice(0);
          //把所有arguments保存下来
          _args = _args.concat(Array.prototype.slice.call(arguments));
          if (_args.length < len) {
              return curry.call(this, fn, _args);
          }
          else {
              return fn.apply(this, _args);
          }
        }
    }
    

    Rest参数解法

    function curry(func) {
      return function curried(...args) {
        if (args.length >= func.length) {
          return func.apply(this, args);
        } else {
          return function(...args2) {
            return curried.apply(this, args.concat(args2));
          }
        }
      };
    }
    

    相关文章

      网友评论

          本文标题:【JS】高阶函数与函数柯里化

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