函数柯里化currying的概念最先由俄国数学家Moses Schönfinkel发明,然后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名。本文将详细介绍函数柯里化(curring)数组
currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数以后,该函数并不会当即求值,而是继续返回另一个函数,刚才传入的参数在函数造成的闭包中被保存起来。待到函数被真正须要求值的时候,以前传入的全部参数都会被一次性用于求值闭包
从字面上理解currying并不太容易,下面经过编写一个计算每个月开销的函数来解释函数柯里化curryingapp
在天天结束以前,都要记录今天花掉了多少钱。代码以下:函数
var monthlyCost = 0; var cost = function( money ){ monthlyCost += money; }; cost( 100 ); // 第 1 天开销 cost( 200 ); // 第 2 天开销 cost( 300 ); // 第 3 天开销 //... cost( 700 ); // 第 30 天开销 alert ( monthlyCost ); // 输出1个月的总开销
天天结束后都会记录并计算到今天为止花掉的钱。但其实并不太关心天天花掉了多少钱,而只想知道到月底的时候会花掉多少钱。也就是说,实际上只须要在月底计算一次this
若是在每月的前29天,都只是保存好当天的开销,直到最后一天才进行求值计算,这样就达到了咱们的要求,代码以下spa
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的函数。若是和上面的例子结合,则这个函数的做用是遍历本月天天的开销并求出它们的总和prototype
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()时,才利用前面保存的全部参数,真正开始进行求值计算code
实际上,柯里化函数不只能够接收要柯里化的函数做为参数,也能够接收一些必要参数,下面是函数柯里化(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)以后,传参的同时伴随着求值的过程,则代码简化以下blog
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"]