简介
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c)
转换为可调用的 f(a)(b)(c)
。把接受多个参数的函数转换成接受一个单一参数的函数。
function curry(fn) { // curry(f) 执行柯里化转换
return function(a) {
return function(b) {
return fn(a, b);
};
};
}
// 用法
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert(curriedSum(1)(2)); // 3
柯里化更高级的实现,例如 lodash
库的 _.curry
,会返回一个包装器,该包装器允许函数被正常调用或者以偏函数(partial)的方式调用:
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // 使用来自 lodash 库的 _.curry
alert( curriedSum(1, 2) ); // 3,仍可正常调用
alert( curriedSum(1)(2) ); // 3,以偏函数的方式调用
实际用途
柯里化实际是把简答的问题复杂化,复杂化的同时,我们在使用函数时拥有了更加多的自由度。
我们会遇到各种需要通过正则检验的需求,比如校验电话号码、校验邮箱、校验身份证号、校验密码等, 这时我们会封装一个通用函数 checkByRegExp
,接收两个参数,校验的正则对象和待校验的字符串。
function checkByRegExp(regExp,string) {
return regExp.test(string);
}
checkByRegExp(/^1\d{10}$/, '123456789'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
如果我们需要校验多个电话号码或者校验多个邮箱呢?我们可能会这样做:
checkByRegExp(/^1\d{10}$/, '123456789'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '987654321'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '111111111'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@qq.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@gmail.com'); // 校验邮箱
我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,相同的正则我们需要写多次, 这就导致我们在使用的时候效率低下。此时,我们可以借助柯里化对 checkByRegExp
函数进行封装,以简化代码书写,提高代码可读性。
/进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
checkCellPhone('18642838455'); // 校验电话号码
checkCellPhone('13109840560'); // 校验电话号码
checkCellPhone('13204061212'); // 校验电话号码
checkEmail('test@163.com'); // 校验邮箱
checkEmail('test@qq.com'); // 校验邮箱
checkEmail('test@gmail.com'); // 校验邮箱
经过柯里化后,我们生成了两个函数 checkCellPhone
和 checkEmail
, checkCellPhone
函数只能验证传入的字符串是否是电话号码, checkEmail
函数只能验证传入的字符串是否是邮箱,它们与原函数checkByRegExp
相比,从功能上通用性降低了,但适用性提升了。 柯里化的这种用途可以被理解为:参数复用。
封装函数
根据柯里化定义得到:
-
当接收的参数数量与原函数的形参数量相同时,执行原函数;
-
当接收的参数数量小于原函数的形参数量时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数
/**
* 将函数柯里化
* @param fn 待柯里化的原函数
* @param len 所需的参数个数,默认为原函数的形参个数
*/
function curry(fn, len = fn.length) {
return _curry.call(this,fn,len)
}
/**
* 中转函数
* @param fn 待柯里化的原函数
* @param len 所需的参数个数
* @param args 已接收的参数列表
*/
function _curry(fn,len,...args) {
return function (...params) {
let _args = [...args, ...params];
if(_args.length >= len){
return fn.apply(this,_args);
} else {
return _curry.call(this,fn,len,..._args)
}
}
}
//test
let _fn = curry(function(a,b,c,d,e){
console.log(a,b,c,d,e)
});
_fn(1,2,3,4,5); // print: 1,2,3,4,5
_fn(1)(2)(3,4,5); // print: 1,2,3,4,5
_fn(1,2)(3,4)(5); // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5
写法二:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
const test = curry((a,b,c,d,e) => {
console.log(a,b,c,d,e);
});
//以下均输出 1,2,3,4,5
test(1,2,3,4,5)
test(1)(2)(3,4,5)
test(1,2)(3,4)(5)
test(1)(2)(3)(4)(5)