###柯里化 柯里化是函数式编程里的一个技巧,按照维基百科里的定义,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。听起来有点绕口,简单来讲就是给函数分步传参。每次传递部分参数给函数后,会返回一个接受剩余参数的函数。请看下述代码
function addAll(a, b) {
return a + b;
}
function addCurry(a) {
return function addCurry1(b) {
return a + b;
}
}
console.log(addAll(2, 3));
console.log(addCurry(2)(3));
一个简单的求和函数addAll,柯里化成addCurry。两者实现的功能是一样的,但是方式略有不同,调用addAll(2,3)求和得到5,调用addCurry(2)会返回一个新的函数,该函数接受剩余的参数3,再次调用返回的函数就得到最终结果5(这里利用的js的闭包)。 ###为什么使用柯里化 也许大家会问,我不用柯里化照样能实现功能,而且实现起来也并不麻烦,那我为什么要用柯里化呢?不急,我们来看看下面的几个例子。 ####非柯里化解题
-
实现一个求和函数,可以接受多个参数
使用可变参数以及reduce可以很简单实现
var add = function(a, b) {
return a + b;
}
var total = function(...rest) {
return rest.reduce(add);
}
console.log(total(2, 3, 4));
-
每个参数减1之后再求和
使用map稍作修改也不难
var add = function(a, b) {
return a + b;
}
var minus = function(c, d) {
return c - d;
}
var total = function(arg, ...rest) {
return rest.map(function(item) {
return minus(item, arg);
}).reduce(add);
}
console.log(total(1, 2, 3, 4));
-
每个参数乘2之后再求和
同上
var add = function(a, b) {
return a + b;
}
var multiplier = function(e, f) {
return e * f;
}
var total = function(arg, ...rest) {
return rest.map(function(item) {
return multiplier(item, arg);
}).reduce(add);
}
console.log(total(2, 2, 3, 4));
发现什么问题了么?在total函数中,我们没有办法同时执行multiplier和minus,代码2切换到代码3,我们需要修改total函数。
柯里化解题
var add = function(a, b) {
return a + b;
}
var minus = function(c) {
return function curryMinus(d) {
return d - c;
}
}
var multiplier = function(e) {
return function curryMultiplier(f) {
return f * e;
}
}
var total = function(curry, ...rest) {
return rest.map(curry).reduce(add);
}
console.log(total(minus(1), 2, 3, 4));
console.log(total(multiplier(2), 2, 3, 4));
将处理参数的函数提取出来(minus和multiplier)并作为参数传递给total,接口清晰了许多,修改起来也更加的方便了,不是吗?
这就是函数式编程的思想,用已有的函数进行组合,生成一个新的函数。柯里化就做到了这一点。 ###柯里化的应用场景
延迟计算
function curryFunc(cb) {
var args = [];
return function curry(...rest) {
if (rest.length == 0) {
return cb(...args);
} else {
args.push(...rest);
return curry;
}
}
};
function add(...rest) {
return rest.reduce(function(a, b) {
return a + b;
});
};
var sum = curryFunc(add);
console.log(sum(2));
console.log(sum(3)(4));
console.log(sum());
curryFunc进行了柯里化,传参调用的时候,会返回一个新的函数curry,新函数curry接受剩余参数,直到最后一次调用sum()会计算求和最终结果。 ####动态创建 例如浏览器添加事件监听,考虑到IE兼容性,我们会写下以下代码
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
这样每次我们调用addEvent都会重复进行一次if else判断,而使用柯里化,我们只需要最开始判断一次就行了,根据判断结果动态生成addEventListener还是attachEvent。
var addEvent = (function() {
if (window.addEventListener) {
return function(el, type, fn, capture) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
}
} else if (window.attachEvent) {
return function(el, type, fn, capture) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
}
})();
在部分计算出结果后(if判断),在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算 ####参数复用 多次调用同一个函数,传递的参数大多数时候相同,那么可以将函数进行柯里化
var obj = {
name: 'test'
};
var foo = function(prefix, suffix) {
console.log(prefix + this.name + suffix);
}.bind(obj, 'currying-');
foo('-function1'); // currying-test-function1
foo('-function2'); // currying-test-function2
bind方法将第一个参数obj设置为foo的执行环境,其他参数 'currying-'传递给foo(函数foo并未执行,其实也可以看成是延迟执行),动态创建返回一个新函数(也是一种动态创建),这个新函数保存了通用参数'currying-'。可以大致写出bind的实现方式:
Function.prototype.bind = function(context, ...args) {
return function(...rest) {
return this.call(context, ...args, ...rest);
}
}