柯里化
什么是柯里化
柯里化(英语:Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的基础
上面的代码其实是一个高阶函数(high-order function), 高阶函数是指操作函数的函数,它接收一个或者多个函数作为参数,并返回一个新函数。此外,还依赖与闭包的特性,来保存中间过程中输入的参数。即:
- 函数可以作为参数传递
- 函数能够作为函数的返回值
- 闭包
通用实现
var currying = function(fn) {
//保存最初函数的第一个参数
var args = [].slice.call(arguments, 1);
return function() {
//将第一个参数与剩余的参数连接
var newArgs = args.concat([].slice.call(arguments));
return fn.apply(null, newArgs);
};
};
柯里化的作用
参数复用
var countNum = currying(function() { var allargs = [].slice.call(arguments); console.log(allargs.join(";")); }, "10"); countNum("1","2","3"); //10;1;2;3 countNum("4","5"); //10;4;5;
延迟计算
var currying = function( fn ){ var args = []; return function(){ if ( arguments.length === 0 ){ return fn.apply( this, args ); }else{ args = args.concat([].slice.call(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 ); // 转化成currying 函数 cost( 100 ); // 未真正求值 cost( 200 ); // 未真正求值 cost( 300 ); // 未真正求值 alert ( cost() ); // 求值并输出:600
提前返回
var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } })();
反柯里化
反柯里化函数,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。
我们常用apply、call让对象去借用一个原本不属于它的方法,例如使用Array.prototype中的slice、push等方法操作数组,这是因为在javascript里面,很多函数都不做对象的类型检测,而是只关心这些对象能做什么。
通用实现
Function.prototype.uncurrying = function () { var self = this; return function() { var obj = Array.prototype.shift.call( arguments ); return self.apply( obj, arguments ); }; };
或者
Function.prototype.uncurrying = function(){ var self = this; return function(){ return Function.prototype.call.apply( self, arguments ); } };
反柯里化的作用
使本来只有特定对象才适用的方法,扩展到更多的对象。把原来已经固定的参数或者this上下文等当作参数延迟到未来传递。
obj.foo( arg1 ) //foo本来是只在obj上的函数. 就像push原本只在Array.prototype上 foo( obj, arg1 ) // 将[].push转换成push( [] )
对arguments使用push
Function.prototype.uncurrying = function () { var self = this; //self为push函数 return function() { var obj = Array.prototype.shift.call( arguments ); //obj为 {"length": 1, "0": 1}; return self.apply( obj, arguments ); //相当于Array.prototype.push.apply(obj, 2) }; }; var push = Array.prototype.push.uncurrying(); var obj = { "length": 1, "0": 1 }; push( obj, 2 ); console.log( obj ); // 输出:{0: 1, 1: 2, length: 2}
把Array.prototype的其他方法“复制”array对象
for ( var i = 0, fn, ary = [ 'push', 'shift', 'forEach' ]; fn = ary[ i++ ]; ){ Array[ fn ] = Array.prototype[ fn ].uncurrying(); }; var obj = { "length": 3, "0": 1, "1": 2, "2": 3 }; Array.push( obj, 4 ); // 向对象中添加一个元素 console.log( obj.length ); // 输出:4 var first = Array.shift( obj ); // 截取第一个元素 console.log( first ); // 输出:1 console.log( obj ); // 输出:{0: 2, 1: 3, 2: 4, length: 3} Array.forEach( obj, function( i, n ){ console.log( n ); // 分别输出:0, 1, 2 });
对arguments使用slice方法
var slice = Array.prototype.slice.uncurrying(); (function(){ alert( slice(arguments, 2) ); // [3, 4] })(1, 2, 3, 4)