函数柯里化(Currying), 是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
简单来说,就是固定一些参数,返回一个接受剩余参数的函数。实质就是使用闭包返回一个延迟执行函数。
其好处主要在于:
- 参数复用,或者说是固定参数,避免重复传参;
- 提前返回,或者说是提前确认,避免重复判断;
- 延迟执行。
函数柯里化的使用原理及方式
从简单例子出发:
实现一个函数能够使 add(1, 2) => 3 转变为 add(1)(2) => 3
function add(num1, num2) {
return num1 + num2;
}
分析: 如果要实现add(num1, num2)到add(num1)(num2)的转变,我们首先要做的就是将add的return改造成一个闭包函数,这样才能实现连续执行且在执行第二个函数的时候,访问到num1参数。
function curriedAdd(num1) {
return function(num2) {
return num1 + num2; // 实际上这就是add(num1, num2);
}
}
也就是说add的curry函数就是
function curry(fn, num1) {
return function(num2) {
return fn(num1, num2)
}
}
const curriedAdd = curry(add, 1);
curriedAdd(2);
将参数抽象一下
function curry(fn) {
const curryArgs = Array.from(arguments).slice(1); // 获取curry函数的参数,也就是上例中的num1,slice(1)是排除掉fn
return function() {
const callbackArgs = Array.from(arguments); // 获取回调函数的参数,也就是上例中的num2
return fn(...callbackArgs, ...curryArgs);
}
}
了解原理之后,我们简化一版
function curry(fn, ...curryArgs) {
return (...callbackArgs) => {
return fn(...callbackArgs, ...curryArgs);
}
}
拓展一下,怎样实现add(num1)(num2)(num3)(num4)...、add(num1, num2)(num3)(num4)、add(num1)(num2)(num3, num4)()呢?
function add(num1, num2, num3, num4) {
return num1 + num2 + num3 + num4;
}
分析:不断递归获取传入参数,直到取到的参数个数等于fn的参数个数为止,最终将获取到的所有参数传给fn并返回执行结果。动手吧!
function curry(fn) {
// 注意一下,如果设置add函数num1、num2、num3、num4 等的初始值,则将会对通过function.length获取function的形参数造成影响
const curryLength = fn.length; // fn.length是为了获取函数的形参个数,用以判断递归是否结束
const curryArgs = Array.from(arguments).slice(1);
return function() {
const callbackArgs = Array.from(arguments);
const currentArgs = [...curryArgs, ...callbackArgs]; // 注意,这里代表了callback函数执行的所有参数
if (callbackArgs.length === 0 || currentArgs.length === curryLength) { // 执行到最后一步,或者参数为空时
return fn(...currentArgs);
} else {
return curry(fn, ...currentArgs);
}
}
}
简化一下
function curry(fn, ...curryArgs) {
return (...callbackArgs) => {
const currentArgs = [...curryArgs, ...callbackArgs];
return fn.length === currentArgs.length || callbackArgs.length === 0 ? fn(...currentArgs) : curry(fn, ...currentArgs);
}
}
这样我们就可以使用
curry(add)(1)(2)(3)(4); => 10
函数柯里化的好处
【复用参数】
比如说我们用正则验证一个手机号。
function curry(fn, ...args) {
return (...callbackArgs) => {
const currentArgs = [...args, ...callbackArgs];
return callbackArgs.length === 0 || currentArgs.length === fn.length ? fn(...currentArgs) : curry(fn, ...currentArgs);
}
}
const phoneReg = /^1[3-9]\d{9}$/;
function _checkPhone(reg, phone) {
return reg.test(phone);
}
console.log(_checkPhone(phoneReg, 19956526362));
// 柯里化
const checkPhone = curry(_checkPhone)(phoneReg); // 这样我们就复用了验证手机的正则,这就是复用参数,或者说是固定参数
checkPhone(19956526362);
checkPhone(16956526362);
【提前返回、延迟执行】
再做一个拓展,我们需要对一个正确的手机号做一系列不同步的操作(同步的话就没有意义了)
function doSomething1(reg, phone, callback) {
reg.test(phone) && callback();
}
function doSomething2(reg, phone, callback) {
reg.test(phone) && callback();
}
doSomething1(phoneReg, 19956526362,callback1);
doSomething2(phoneReg, 19956526362,callback2);
// 既然是对同一个号码做判断,我们当然可以先将判断结果保存下来,这样就不用每次都做判断了
function _doSomething(reg, phone, callback) {
reg.test(phone) && callback();
}
const doSomething = curry(_doSomething)(19956526362); // 这里就是提前返回电话号码是否正确了
doSomething(callback1); // 这里就是延迟执行
doSomething(callback2);