zoukankan      html  css  js  c++  java
  • JS对象类型函数进阶篇函数柯里化

    函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名。

    定义

    currying又称部分求值。柯里化函数首先会接受一些参数,参数接收之后不会立即求值,而是继续返回一个新的函数,之前传入的参数会在新函数形成的闭包中保存起来,等到函数真正需要求值的时候,之前传入的所有参数会被一次性用于求值。

    概念不好理解,举例说明

    【计算每月的开销】有一项任务,要求计算出每月的开销。最简单的方法肯定是记录下每天的开销,然后月末的时候加到一起就行了,于是可以这样写:

    var totalMoney = 0;
    
    var cost = function(money) {
      totalMoney += money
    }
    
    cost(100) // 第1天消费
    cost(200) // 第2天消费
    // ...
    cost(100) // 第30天消费
    console.log(totalMoney) // 当月消费金额
    

    上面的函数计算了每天的开销,但是实际我只需要知道月末的消费就好了,其他时间的消费总和我并不关心,于是可以这样改写:

    var cost = (function(){
    var args = [];
    return function() {
     if(arguments.length === 0) { // 如果没有参数,求和并返回结果
       var totalMoney = 0;
       for(var i = 0, len = args.length; i < len; i++) {
          totalMoney += args[i]
       }
       return totalMoney
     } else { // 如果有参数,把参数添加到数组
       [].push.apply(args, arguments)
     }
    }
    })()
    
    cost(100) // 不求值
    cost(200) // 不求值
    cost(100) // 不求值
    cost() // 400
    

    举这个例子只是便于理解概念,并不能体现柯里化函数的强大之处,它真正的用处是函数式编程。

    通用函数

    把上面的例子改写成一个通用的柯里化函数,把一个将要被柯里化的函数作为参数。

    var currying = function (fn) {
        var args = [];
        return function () {
          if (arguments.length === 0) { // 如果没有传参就进行求值
            return fn.apply(this, args);
          } else {
            // 把fn的参数展开,添加到数组里面
            [].push.apply(args, arguments);
          }
        }
      };
    
      var totalCost = (function () {
        var totalMoney = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            totalMoney += arguments[i];
          }
          return totalMoney;
        }
      })();
    
      var cost = currying(totalCost); // 转化成 currying 函数
      cost(100); // 未真正求值 
      cost(200); // 未真正求值 
      cost(100); // 未真正求值 
      console.log(cost()); // 400 
    

    可传参的函数

    柯里化函数不仅可以接受一个将要被柯里化的函数作为参数,也可以接受一些参数。

      var currying = function (fn) {
        var args = [].slice.call(arguments, 1); // 获取除fn之外的其他参数
        return function () {
          if (arguments.length === 0) {
            return fn.apply(this, args);
          } else {
            [].push.apply(args, arguments);
          }
        }
      };
    
      var totalCost = (function () {
        var totalMoney = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            totalMoney += arguments[i];
          }
          return totalMoney;
        }
      })();
    
      var cost = currying(totalCost, 100, 200);
      cost(100);
      cost(200);
      cost(100);
      console.log(cost()); // 700  
    

    求值柯里化

    柯里化函数传参的同时,可以进行求值。

      var currying = function (fn) {
        var args = [].slice.call(arguments, 1);// 获取除fn外的其他参数
        return function () {
         // 把fn里面的参数转换为数组
         var innerArgs = [].slice.call(arguments)
         // 合并参数
         var finalArgs = args.concat(innerArgs);
         // 把参数传给fn函数
         return fn.apply(null, finalArgs);
        }
      };
    
      var totalCost = (function () {
        var totalMoney = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            totalMoney += arguments[i];
          }
          return totalMoney;
        }
      })();
    
      var cost = currying(totalCost, 100, 200);
      console.log(cost(100)); // 100 + 200 + 100 = 400
      console.log(cost(100,200)); // 400 + 100 + 200 + 100 + 200 = 1000 
    

    反柯里化

    介绍call()和apply()方法时说过,它们都可以改变函数中this的指向,这也是为什么数组的方法可以用到对象上。反柯里化就是为了把泛化的this提取出来,扩大方法的适用范围,使本来只能用于特定对象的方法扩展到更多的对象。

    下面是两种实现uncurrying的方法:

    Function.prototype.uncurrying = function(){
      var _this = this;
      return function(){
        return Function.prototype.call.apply(_this, arguments)
      }
    }
    
    Function.prototype.uncurrying = function(){
      var _this = this;
      return function(){
        var obj = Array.prototype.shift.call(arguments)
          return _this.apply(obj, arguments)
        }
    }
    

    这两种方式的原理是一样的,都是把this.method(arg1, arg2)转换成method(this, arg1, arg2)

    【示例】让对象拥有push()方法

    var push = [].push.uncurrying(), obj = {}, arr = [];
    push(obj, 'hello', 'world')
    push(arr, 'hello', 'world')
    console.log(obj, arr) // {0: 'hello', 1: 'world', length: 2}  ['hello', 'world']
    

    【示例】让数组拥有toUpperCase方法

    var toUpperCase = String.prototype.toUpperCase.uncurrying(), arr = ['hello','world'];
    console.log(arr.map(toUpperCase)) // ['HELLO', 'World']
    
    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    C# EntityFramework 入门之 Code First
    import cx_Oracle ImportError: DLL load failed: 找不到指定的模块。
    unnitest用例按顺序执行方法总结
    Grid + selenium分布式执行自动化测试
    selenium自动化测试工具的使用总结
    使用SQLAlchemy操作已存在的数据库的表
    map()函数 lambda函数 zip()函数的使用
    使用SQLAlchemy操作MySQL以及执行原生的sql语句
    python创建文件/文件夹,判断文件/文件夹是否存在以及os.path模块的使用
    python函数的四种参数定义方式和传递方式
  • 原文地址:https://www.cnblogs.com/yesyes/p/15351910.html
Copyright © 2011-2022 走看看