一、函数柯里化
currying :是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
例:记录程序员一个月的加班总时间,那么好,我们首先要做的是记录程序员每天加班的时间,然后把一个月中每天的加班的时间相加,就得到了一个月的加班总时间。
var monthTime = 0;
function overtime(time) {
return monthTime += time;
}
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log(monthTime); // 10.1
下面的overtime函数还不是一个柯里化函数的完整实现,但可以帮助我们了解其核心思想:
var overtime = (function() {
var args = [];
return function() {
if(arguments.length === 0) {
var time = 0;
for (var i = 0, len = args.length; i < len; i++) {
time += args[i];
}
return time;
}
else {
[].push.apply(args, arguments);
}
}
})();
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log( overtime() ); // 10.1
例:使用currying与不使用进行对比
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
(1)参数复用
/*正常正则验证字符串 reg.test(txt)*/
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
console.log(check(/\d+/g, 'test')); //false
console.log(check(/[a-z]+/g, 'test')); //true
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g);
var hasLetter = curryingCheck(/[a-z]+/g);
console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest')); // false
console.log(hasLetter('21212')); // false
(2)提前确认
//第一种:最常见封装dom的方法
var on = function(element, event, handler) {
if (document.addEventListener) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
}
else {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
}
}
//第二种:相对于第一种来说,就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断
var on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
}
else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
//第三种:把isSupport这个参数给先确定下来了
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
}
else {
return element.attachEvent('on' + event, handler);
}
}
(3)延迟运行
js中经常使用的bind,实现的机制就是currying:
Function.prototype.bind=function(context){ //context为我们想修正的this对象
var self=this; //保存原函数
var args = Array.prototype.slice.call(arguments, 1);
return function(){ //返回一个新函数,实际上执行时会先执行这个新函数
return self.apply(context,args); //执行原函数,指定context为原函数体内的this
}
};
通用的封装方式:
// 初步封装
var currying = function(fn) { //fn为要进行柯里化的函数
var args = Array.prototype.slice.call(arguments, 1); //args获取第一个方法内的全部参数
return function() {
var newArgs = args.concat(Array.prototype.slice.call(arguments)); //将后面方法里的全部参数和args进行合并
return fn.apply(this, newArgs); //把合并后的参数通过apply作为fn的参数并执行
}
}
//使用方式
function add(num1,num2){
return num1*num2;
}
var curriedAdd=currying(add,5);
console.log(curriedAdd(8)); //40
多参数的封装:
// 支持多参数传递
function progressCurrying(fn, args) {
var self = this;
var len = fn.length;
var args = args || [];
return function () {
var newArgs = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, newArgs);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (newArgs.length < len) {
return progressCurrying.call(self, fn, newArgs);
}
return fn.apply(this, newArgs); // 参数收集完毕,则执行fn
}
}
function add() {
var args = Array.prototype.slice.call(arguments); // 第一次执行时,定义一个数组专门用来存储所有的参数
var adder = function() {
args.push(...arguments); // 在内部声明一个函数,利用闭包的特性保存args并收集所有的参数值
return adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
adder.toString = function () {
return args.reduce(function (a, b) {
return a + b;
});
};
return adder;
}
console.log(add(1)(2)(3)); //6
console.log(add(1,2,3)(4)); // 10
console.log(add(1)(2)(3)(4)(5)); // 15
console.log(add(2,6)(1)); // 9
二、函数反柯里化
当我们调用对象的某个方法时,不用去关心该对象原本是否被设计为拥有这个方法,只要这个方法适用于它,我们就可以对这个对象使用它。
例:使用uncurrying将泛化this的过程提取出来
//分析一下调用Array.prototype.push.uncurring()这句代码时,发生了什么事情:
Function.prototype.uncurring = function() {
var self = this; //self此时是Array.prototype.push
return function() {
var obj = Array.prototype.shift.call(arguments);
//obj 是{
// "length": 1,
// "0": 1
//}
//arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2]
return self.apply(obj, arguments); //相当于Array.prototype.push.apply(obj, 2);
};
};
//测试一下
var push = Array.prototype.push.uncurring();
var obj = {
"length": 1,
"0" : 1
};
push(obj, 2);
console.log( obj ); //{0: 1,1: 2, length: 2 }
另一种实现方式:
Function.prototype.uncurring=function(){
var self=this;
return function(){
return Function.prototype.call.apply(self,arguments);
}
};
例:把Array.prototype.push方法转换成一个通用的push函数
var push = Array.prototype.push.uncurring();
//测试一下
//arguments本来是没有push方法的,通常,我们都需要用Array.prototype.push.call来实现push方法,但现在,直接调用push函数,既简洁又意图明了。
(function() {
push(arguments, 4);
console.log(arguments); //[1, 2, 3, 4]
})(1, 2, 3);