函数柯里化是所有编程语言推崇的函数优化方式,js的函数柯里化是你写出优雅函数的基础。
概念
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
例如:我们求(a+b)*c 如果普通方法实现 f(a,b,c),用柯里化方法:f(a)(b)©这种调用格式获取。
实现原理
- 闭包来保存参数;
- 高阶函数实现运算;
例如最简单的例子:求 (15+3)*4的值
let calcu = (a, b, c) => (a+b)*c;
function curry(fn, args){
let len = fn.length;
let _this = this;
let _args = args || [];
return function(){
let args = Array.prototype.slice.apply(arguments);
args = Array.prototype.concat.call(_args, args);
// 当接收到的参数小于fn所需参数个数时,继续接收参数
if(args.length < len){
return curry.call(_this, fn, args);
}
return fn.apply(this, args);
}
}
let add = curry(calcu);
console.log(add(15)(3)(5)); //72
在上面的代码中,我们使用curry函数它可以不用一次性接收三个参数,而是慢慢接收,当发现接收到的参数达到3个之后再返回结果。这就是参数复用(通过例子我们可以看到,实现这个特点主要原因是利用了闭包,将接收到的参数存于_args中,由于闭包的原因这些参数在函数执行完之后并不会被释放掉。
上面的curry方法,将每次调用fn时读入的参数用args来保存,并将本次读入的参数args与当前一共拥有的所有参数_args用concat方法连接起来,当参数个数符合fn的参数个数要求时,则调用fn。
特点
- 参数复用: 就是利用闭包的原理,让我们前面传输过来的参数不要被释放掉。
- 提前确认: 利用柯里化特性对某些全局方法进行改写,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
- 延迟运行:利用闭包将函数的定义和执行环境分开。
参数复用案例:
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
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)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
提前确认:
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);
}
}
延迟运行:
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _this.apply(context, args)
}
}
柯里化函数的最佳实现
函数柯里化是一个比较难以应用的方法,最主要是能够理解其原理使你阅读源码无障碍,下面给出一个柯里化的最佳实现方法,背过他可以救急
// 支持多参数传递
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}