深入理解javascript函数进阶系列第二篇——函数柯里化

前面的话

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

定义

  currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值

  从字面上理解currying并不太容易,下面通过编写一个计算每月开销的函数来解释函数柯里化currying

每月开销函数

  在每天结束之前,都要记录今天花掉了多少钱。代码如下:

var monthlyCost = 0;
var cost = function( money ){
  monthlyCost += money;
};
cost( 100 ); // 第 1 天开销
cost( 200 ); // 第 2 天开销
cost( 300 );   // 第 3 天开销
//...
cost( 700 );   // 第 30 天开销
alert ( monthlyCost );     // 输出1个月的总开销

  每天结束后都会记录并计算到今天为止花掉的钱。但其实并不太关心每天花掉了多少钱,而只想知道到月底的时候会花掉多少钱。也就是说,实际上只需要在月底计算一次

  如果在每个月的前29天,都只是保存好当天的开销,直到最后一天才进行求值计算,这样就达到了我们的要求,代码如下

  var cost = (function () {
    var args = [];
    return function () {
      //如果没有参数,则计算args数组中的和
      if (arguments.length === 0) {
        var money = 0;
        for (var i = 0, l = args.length; i < l; i++) {
          money += args[i];
        }
        return money;
        //如果有参数,则只能是将数据传到args数组中
      } else {
        [].push.apply(args, arguments);
      }
    }
  })();
  cost(100); // 未真正求值
  cost(200); // 未真正求值
  cost(300); // 未真正求值
  console.log(cost()); // 求值并输出:600

通用函数

  下面来编写一个通用的柯里化函数currying,currying接受一个参数,即将要被currying的函数。如果和上面的例子结合,则这个函数的作用是遍历本月每天的开销并求出它们的总和

  var currying = function (fn) {
    var args = [];
    return function () {
      if (arguments.length === 0) {
        return fn.apply(this, args);
      } else {
        [].push.apply(args, arguments);
        return arguments.callee;
      }
    }
  };
  var cost = (function () {
    var money = 0;
    return function () {
      for (var i = 0, l = arguments.length; i < l; i++) {
        money += arguments[i];
      }
      return money;
    }
  })();
  var cost = currying(cost); // 转化成 currying 函数
  cost(100); // 未真正求值
  cost(200); // 未真正求值
  cost(300);   // 未真正求值
  alert(cost());  // 求值并输出:600

  至此,完成了一个currying函数的编写。当调用cost()时,如果明确地带上了一些参数,表示此时并不进行真正的求值计算,而是把这些参数保存起来,此时让cost函数返回另外一个函数。只有以不带参数的形式执行cost()时,才利用前面保存的所有参数,真正开始进行求值计算

可传参函数

  实际上,柯里化函数不仅可以接收要柯里化的函数作为参数,也可以接收一些必要参数,下面是函数柯里化(currying)的改进代码

  var currying = function (fn) {
    var args = [];
    //储存传到curring函数中的除了fn之外的其他参数,并储存到args函数中
    args = args.concat([].slice.call(arguments,1));
    return function () {
      if (arguments.length === 0) {
        return fn.apply(this, args);
      } else {
        //将fn中的参数展开,然后再储存到args数组中
        [].push.apply(args, arguments);
      }
    }
  };
  var cost = (function () {
    var money = 0;
    return function () {
      for (var i = 0, l = arguments.length; i < l; i++) {
        money += arguments[i];
      }
      return money;
    }
  })();
  var cost = currying(cost,100,200); // 转化成 currying 函数
  cost(100,200); // 未真正求值
  cost(300);   // 未真正求值
  console.log((cost()));  // 求值并输出:900

求值柯里化

  如果函数柯里化(curring)之后,传参的同时伴随着求值的过程,则代码简化如下

  var currying = function (fn) {
    //获取除了fn之外的其他参数
    var args = [].slice.call(arguments, 1);
    return function () {
      //获取fn里的所有参数
      var innerArgs = [].slice.call(arguments);
      //最终的参数列表为args和innerArgs的结合
      var finalArgs = args.concat(innerArgs);
      //将finalArgs里的参数展开,传到fn中执行
      return fn.apply(null, finalArgs);
    };
  };
  var cost = (function () {
    var money = 0;
    return function () {
      for (var i = 0, l = arguments.length; i < l; i++) {
        money += arguments[i];
      }
      return money;
    }
  })();
  var cost = currying(cost,100,200); // 转化成 currying 函数
  cost(300);//100+200+300=600
  cost(100,100);//(100+200+300)+(100+200+100+100)=1100

反柯里化

  Array.prototype上的方法原本只能用来操作array对象。但用call和apply可以把任意对象当作this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性

  有没有办法把泛化this的过程提取出来呢?反柯里化(uncurrying)就是用来解决这个问题的。反柯里化主要用于扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

  uncurrying的话题来自JavaScript之父Brendan Eich在2011年发表的一篇文章。以下代码是 uncurrying 的实现方式之一:

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

  另一种实现方法如下

Function.prototype.currying = function() {
    var _this = this;
    return function() {
        return Function.prototype.call.apply(_this, arguments);
    }
}

  最终是都把this.method转化成method(this,arg1,arg2....)以实现方法借用和this的泛化

  下面是一个让普通对象具备push方法的例子

 var push = Array.prototype.push.uncurrying(),
    obj = {};
  push(obj, ‘first‘, ‘two‘);
  console.log(obj);
/*obj {
    0 : "first",
    1 : "two"
}*/

  通过uncurrying的方式,Array.prototype.push.call变成了一个通用的push函数。这样一来,push函数的作用就跟Array.prototype.push一样了,同样不仅仅局限于只能操作array对象。而对于使用者而言,调用push函数的方式也显得更加简洁和意图明了

  最后,再看一个例子

var toUpperCase = String.prototype.toUpperCase.uncurrying();
console.log(toUpperCase(‘avd‘)); // AVD
function AryUpper(ary) {
    return ary.map(toUpperCase);
}
console.log(AryUpper([‘a‘, ‘b‘, ‘c‘])); // ["A", "B", "C"]
时间: 2024-12-07 09:05:58

深入理解javascript函数进阶系列第二篇——函数柯里化的相关文章

深入理解javascript选择器API系列第二篇——getElementsByClassName

× 目录 [1]使用 [2]classList [3]扩展 前面的话 既然有getElementById()和getElementsByTagName()方法,为什么没有getElementsByClassName()呢?id属性.标签名.class属性并没有什么优劣之分啊.终于,HTML5新增了getElementsByClassName()方法,由于在CSS布局中类名的广泛使用,该方法正好切中痛点,使得通过类名选取元素不再麻烦,成为最欢迎的一个方法.接下来,本文将详细介绍该方法 使用 HTM

Scheme高阶函数之函数作为返回值暨currying/柯里化

1.4.1currying/柯里化 通常,编程的函数可以有一个参数列表,而λ表达式要求单参数.所以,currying/柯里化--多个参数的函数转化为只有一个参数的多个函数的连续调用,需要函数作为返回值. 有λ表达式,λx. λy. ( 2x+3y) (define (F x y)(+ ( * 2 x) (* 3 y)));;;等价于下面的表示 (define F (lambda ( x y) (+ ( * 2 x) (* 3 y)) ) ) (F 2 3)            → 13 函数F

深入理解DOM事件机制系列第二篇——事件处理程序

× 目录 [1]HTML [2]DOM0级 [3]DOM2级[4]IE[5]总结 前面的话 事件处理程序又叫事件侦听器,实际上就是事件的绑定函数.事件发生时会执行函数中相应代码.事件处理程序有HTML事件处理程序.DOM0级事件处理程序.DOM2级事件处理程序和IE事件处理程序四类,下面将详细介绍该部分内容 HTML事件处理程序 某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定.这个特性的值应该是能够执行的JavaScript代码 在事件处理程序函数内部,this

深入理解表单脚本系列第二篇——表单字段

× 目录 [1]访问 [2]属性 [3]方法[4]事件 前面的话 表单字段又叫表单元素,表示表单所包含控件,如<input>.<select>等.本文将详细介绍表单字段的内容 访问 每个表单都有elements属性,该属性是表单中所有元素的集合.这个elements集合是一个有序列表,其中包含着表单中的所有字段,如<input>.<textarea>.<button>和<fieldset> 每个表单字段在elements集合中的顺序,

JavaScript的柯里化函数

柯里化,或者说部分应用,是一种函数式编程的技术,对于熟悉以传统方式编写 JavaScript 代码的人来说可能会很费解.但如果使用得当,它可以使你的 JavaScript 函数更具可读性. 更具可读性和灵活性 函数式 JavaScript 被吹捧的优点之一就是拥有短小紧凑的代码风格,可以用最少行数.更少重复的代码得到正确的结果.有时这会以牺牲可读性为代价:如果你还不熟悉函数式编程的方法,这种方法写的代码会很难阅读和理解. 如果之前你遇到过柯里化这个术语,但是不知道它是什么意思,把它当做奇怪的.难

浅析 JavaScript 中的 函数 currying 柯里化

原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字命名). 柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果.因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程. 柯里化一个求和函数 按照分步求值,我们看一个

精读JavaScript模式(六),Memoization模式与函数柯里化的应用

假期就这么结束了!十天假就有三天在路上,真的难受!想想假期除了看了两场电影貌似也没做什么深刻印象的事情.流浪地球,特效还是很赞,不过对于感情的描写还是逃不掉拖沓和尴尬的通病,对于国产科幻还是抱有支持的态度.疯狂的外星人相比读大学期间看的疯狂的赛车,荒诞感还是差了点,也许是我笑点太高...不过整体还是感觉比流浪地球值票价,个人观点吧. 开年来同事说自己小舅子年终奖税后18W,他这两天深受打击,我听完也深受打击,哎. 假期结束也该好好安排下今年的时间了,年底辞职的规划不变,加上未来几年要结婚,想想还

javascript柯里化及组合函数~

大家是不是看我上篇博文有点蒙.用的的curry和compose是什么鬼,怎么那么神奇.上篇博文也是主要用到了这两个函数.那今天我们来聊一下curry和compose,这两个东西是函数式编程很重要的东西,基本每个稍微复杂点的例子都要涉及这两个函数.什么是curry呢?---函数柯里化.就是这个东西了.举一个简单的例子. var _console=curry(function(x){ console.log(x); }) _console("hello"); //hello 其实就这个作用

从 ES6 高阶箭头函数理解函数柯里化

前言:第一次看到多个连续箭头函数是在一个 react 项目中,然鹅确认了下眼神,并不是对的人,因为看得一脸懵逼.em......于是开始各种搜索,先是知道了多个连续箭头函数就是 es6 的多次柯里化的写法,对于函数柯里化,很久以前就知道这个名次,但是并不理解,也没有去了解.为了弄明白多个连续箭头函数,开始了简化之路. 首先看到了这样的一个例子: let add = a => b => a + b 以上是一个很简单的相加函数,把它转化成 ES5 的写法如下: let add = function