柯里化函数的核心是逐步接收参数直至满足原函数参数数量后执行;通过闭包递归判断已收集参数长度与fn.length的关系,不足则返回新函数继续收集,满足则执行原函数。

柯里化函数的核心是“逐步接收参数,直到参数数量满足原函数要求后才执行”。递归收集参数的关键在于:每次传入部分参数时,返回一个新函数,该函数记住已传参数,并等待剩余参数;当累计参数够了,就调用原函数;不够,则继续返回新函数——这个过程天然适合用递归(或更常见的是闭包+递归判断)实现。
用闭包 + 递归判断参数数量
不依赖外部计数器,而是每次调用时对比已收集参数长度与目标参数个数:
- 定义一个包装函数 curry(fn),获取原函数期望的参数个数 fn.length
- 返回的柯里化函数接收若干参数(...args),将其与之前收集的参数合并
- 若合并后总长度 ≥ fn.length,直接执行 fn(...args)
- 否则,返回一个新的函数,该函数内部仍调用自身(形成逻辑递归),并把当前所有参数“记在闭包里”
示例代码:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
// 使用
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
用递归函数显式传递已收集参数
更清晰体现“递归收集”逻辑:把已积累的参数作为递归函数的参数显式传递,避免多层闭包嵌套:
立即学习“Java免费学习笔记(深入)”;
- 主函数 curry(fn) 初始化空数组 collected = []
- 定义内部递归函数 collect(args),将新参数追加到 collected,再判断是否满足
- 满足则执行;不满足则返回一个新函数,调用 collect 并传入新参数
- 注意:每次返回的新函数都共享同一份 collected,需确保不被意外修改
处理 this 和多态调用场景
实际中要注意上下文绑定和多次调用兼容性:
- 用 fn.bind(this, ...args) 可提前绑定部分参数,但需配合 length 判断做兜底
- 若原函数是动态参数(如含 ...rest),fn.length 只反映非 rest 参数个数,需额外约定或改用配置式柯里化
- 支持立即执行(如 f(1)(2, 3)())时,可约定空参数调用表示“触发执行”,即 if (args.length === 0) 就执行
为什么不是字面意义的“递归调用栈很深”?
柯里化中的“递归”通常指逻辑上的重复构造函数,而非深度递归调用。每次返回新函数是常数时间操作,不会导致栈溢出。真正的递归发生在用户连续调用(如 f(1)(2)(3)(4)),但 JS 引擎会优化这种链式函数调用,只要不人为在单次函数内无限递归即可。


















