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']
    
    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    java web项目打包.war格式
    version 1.4.2-04 of the jvm is not suitable for thi
    Sugarcrm Email Integration
    sharepoint 2010 masterpage中必须的Content PlaceHolder
    微信开放平台
    Plan for caching and performance in SharePoint Server 2013
    使用自定义任务审批字段创建 SharePoint 顺序工作流
    Technical diagrams for SharePoint 2013
    To get TaskID's Integer ID value from the GUID in SharePoint workflow
    how to get sharepoint lookup value
  • 原文地址:https://www.cnblogs.com/yesyes/p/15351910.html
Copyright © 2011-2022 走看看