柯里化:让函数"慢慢来",一次只吃一口
在编程世界里,我们常常会遇到这样一种场景:一个函数需要多个参数才能完成任务。但有时候,这些参数并不会一下子全部准备好------可能今天知道第一个,明天拿到第二个,后天才凑齐全部。
这时候,柯里化(Currying) 就派上用场了。它就像一位耐心的厨师,不急着把整道菜做完,而是先记住你已经给的食材,等你把剩下的材料陆续送来,再一锅炒好。
从最简单的加法说起
假设我们有一个普通的加法函数:
js
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 输出 3
这很直接:两个数一起传进去,立刻出结果。但如果我只能先给你 a,过一会儿再告诉你 b 呢?普通函数就无能为力了。
于是我们可以手动"柯里化"一下:
js
function add(a) {
return function(b) {
return a + b;
};
}
console.log(add(1)(2)); // 输出 3
你看,现在 add(1) 返回的是一个新函数,这个函数"记得"了 a = 1,等你再调用它传入 b,就能算出结果。这背后靠的是 闭包------内部函数可以"记住"外部函数的变量,即使外部函数已经执行完了。
这种写法虽然可行,但只适用于固定两个参数的情况。如果函数有三个、四个甚至更多参数,手动嵌套写起来会非常繁琐,而且难以复用
自动柯里化:通用解决方案
手动为每个函数写柯里化版本太麻烦了。有没有办法写一个"万能工具",自动把任意多参函数变成可逐步传参的形式?
当然有!来看这个通用的 curry 函数:
js
function add(a, b, c, d) {
return a + b + c + d;
}
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args); // 参数够了,直接执行
}
return (...rest) => curried(...args, ...rest); // 不够?继续收
};
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)(4)); // 10
console.log(curriedAdd(1, 2)(3, 4)); // 10,也可以一次传多个
它是怎么工作的?
fn.length是 JavaScript 中函数的一个属性,表示该函数声明时定义的参数个数(不包括剩余参数)。- 每次调用
curried,都会收集当前传入的参数(通过...args)。 - 如果当前参数数量 ≥ 原函数所需参数数量,就立即执行
fn(...args)。 - 否则,返回一个新函数,这个新函数会把已有的
args和后续传入的rest合并,再次调用curried------ 这就是递归的思想。
只要收集到足够数量的参数,就立刻执行;否则,返回一个新函数继续等待。
🔍 注意 :这个实现利用了 闭包 + 递归 的思想。每次调用都把已有的参数"存起来",直到攒够为止。而闭包保证了这些中间参数不会丢失。
实战:日志函数的柯里化妙用
柯里化不只是炫技,它在实际开发中非常有用。比如处理日志:
js
const log = type => message => {
console.log(`${type}: ${message}`);
};
const errorLog = log('ERROR');
const infoLog = log('info');
errorLog('接口异常'); // 输出: ERROR: 接口异常
infoLog('页面加载完成'); // 输出: info: 页面加载完成
这里,log 是一个柯里化函数。我们先固定日志类型(如 'ERROR'),得到一个专门打错误日志的函数 errorLog。以后只要传消息内容就行,不用每次都写类型。
优势在哪里?
- 减少重复代码 :不需要每次写
log('error', 'xxx')。 - 提高可读性 :
errorLog('xxx')比log('error', 'xxx')更直观。 - 便于组合与复用:可以轻松创建不同级别的日志器,并在多个模块中共享。
总结:柯里化的三大核心
- 闭包:保存已传入的参数,不会被垃圾回收。
- 递归/链式调用:每次返回新函数,继续接收剩余参数。
- 退出条件 :当参数数量达到原函数要求(
fn.length),就执行并返回结果。
柯里化不是必须用的技术,但它能让你的函数更灵活、更具组合性。就像乐高积木------你可以先拼好一部分,等需要时再接上其他模块,最终搭出完整作品。
下次当你发现某个函数总是在不同地方传相同的前几个参数时,不妨试试柯里化------让函数学会"等一等",说不定代码会变得更优雅!