柯里化是将多参函数 f(a,b,c) 转换为单参链式调用 f(a)(b)(c),每次返回记忆前置参数的函数,直至参数收齐才执行;核心依赖闭包而非简单切分参数。

什么是柯里化?它不是“把函数变短”而是“拆成一串单参函数”
柯里化不是语法糖,也不是为了炫技。它的核心行为是:把 f(a, b, c) 变成 f(a)(b)(c),每次只收一个参数,返回下一个函数,直到所有参数收齐才真正执行。关键点在于——返回的每个中间函数都“记住”了前面传入的参数(闭包),而不是简单地把参数数组切片。
容易踩的坑:
• 把 bind 当柯里化用(f.bind(null, a) 固定第一个参数,但不支持链式调用到最终求值)
• 误以为 (...args) => args.length === n && f(...args) 就是柯里化(这其实是“惰性求值”,没做参数分流)
• 忽略函数的 length 属性可能被箭头函数或重命名破坏,导致自动推断参数个数失效
手写通用柯里化函数:用 length 推断参数数量
最稳妥的做法是显式传入期望参数总数,或依赖原函数的 length(它返回形参个数,不含 rest 参数)。以下是一个健壮的实现:
function curry(fn, arity = fn.length) {
return function curried(...args) {
if (args.length >= arity) {
return fn.apply(this, args);
}
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
使用场景:
• 需要复用部分参数(如 const add10 = curry(add)(10))
• 构建配置化工具函数(如 const formatUSD = curry(formatMoney)('en-US')('USD'))
• 在 React 中避免内联函数重复创建(onClick={curry(handleClick)(id)})
注意:
• 箭头函数没有 arguments 和 length 的可靠值,慎用 fn.length 自动推断
• this 绑定需按需处理,上述版本保留原始调用上下文
• 如果原函数有默认参数(如 (a, b = 1, c)),length 仍为 2,但实际可能需要 3 次调用才能触发执行——得靠文档或手动指定 arity
用 Lodash 的 curry 时为什么有时“卡住”不执行?
Lodash 的 _.curry 默认基于 func.length 判断是否该求值,但它有个隐藏行为:遇到 undefined 占位时不计入已传参数数。比如:
const f = _.curry((a, b, c) => a + b + c); f(1)(2)(3); // ✅ 6 f(1, 2)(3); // ✅ 6 f(1)(undefined)(3); // ❌ 返回函数,不执行 —— undefined 被忽略,只算传了 1 个参数
常见错误现象:
• 函数一直返回新函数,控制台打印出来像 [Function: curried],但就是不计算结果
• 在测试中传 null 或 undefined 作为占位符,结果逻辑中断
• 和 TypeScript 联用时,类型推导把中间态标成 Function,失去类型安全
解决办法:
• 显式传 _.curry(f, 3) 锁死参数数
• 避免用 undefined 占位,改用 _.placeholder(f(1)(_.placeholder)(3))
• 更推荐用 _.curryRight 处理“右对齐”参数场景(如 map(collection, iteratee),想先固定 iteratee)
柯里化和偏函数(Partial Application)根本不是一回事
这是最容易混淆的点。partial 是“填一部分,立即返回新函数”,不管填满没填满;而 curry 是“填一个、等一个,直到填满才执行”。例如:
const add = (a, b, c) => a + b + c; const partialAdd = _.partial(add, 1); // → (b, c) => 1 + b + c const curryAdd = _.curry(add)(1); // → (b) => (c) => 1 + b + c
性能影响:
• 柯里化每次调用都新建函数,高频场景(如渲染循环、事件回调)要考虑缓存或改用 bind
• 偏函数通常只生成一次,开销更小
• 如果只是固定前几个参数,bind(add.bind(null, 1))比柯里化更轻量,且无递归调用栈风险
真正需要柯里化的时刻其实不多:当调用方无法一次性提供全部参数,且你希望 API 表达“逐步确认”的语义时(比如 DSL 解析、配置组装、函数组合 compose(f, g) 的输入适配)。其他时候,先问自己一句:我是不是其实只需要 partial 或 bind?










