算法 - 函数柯里化 (Currying)
1. 题目
实现一个通用柯里化函数 curry(fn),把一个「需要 n 个参数」的函数,转换成可以一次传一部分参数、持续返回自身、直到收集够 n 个参数后才真正调用原函数的函数。
示例:
1 | const add = (a, b, c) => a + b + c; |
进阶要求:支持占位符 curry._,允许跳过某参数稍后再填:
1 | const _ = curry._; |
2. 解题思路
柯里化的本质是闭包累积参数。
2.1 基础版(按 arity 触发)
- 用
fn.length得到原函数所需参数个数n。 - 返回一个
acc函数,每次调用把新参数并入已累积的args(闭包持有)。 - 若
args.length >= n,用fn.apply(this, args)真正执行;否则返回一个把新参数继续累积的函数(保持链式)。
判断「够了就执行、没够就返回函数」是核心。为了让 curried(1) 后还能 curried(1)(2),返回的必须是函数——用 function () { return acc(...args.concat(...arguments)); } 包一层。
2.2 占位符版
在累积参数后,做两步清洗:
- 记录本次调用是否以「尾部实参」结束,用于决定何时可执行。
- 只有当「非占位符参数数量 >= n」时才执行;执行前把参数按先前逻辑压缩(去掉多余的前导
_),保证位置对应。
这部分逻辑较绕,面试能写基础版 + 口头讲清占位符思路已足够。
- 时间复杂度:每次调用 O(已收集参数)。
3. TypeScript 实现
3.1 基础版
1 | function curry(fn: Function) { |
3.2 支持占位符版
1 | const _ = Symbol("placeholder"); |
4. 面试延伸
- 柯里化 vs 部分应用(partial):柯里化把「多元函数」拆成「一连串一元函数」;部分应用是一次固定若干参数。JS 里
bind天然就是部分应用:fn.bind(null, 1)。 - 实战价值:参数复用、延迟执行、函数组合(
compose/pipe需要一元函数)、创建更专用的小函数(如把通用的log(date, level, msg)柯里化成errorLog = log(now, 'error'))。 - TS 类型级柯里化是炫技加分项:用递归条件类型
Curry<F>依据Parameters<F>推导出重载签名,展示你对类型体操的掌握。 - 常见追问:为什么判断用
>=而非===?(箭头函数/默认参数会让fn.length变小,容错用>=更稳);fn.length与arguments.length的区别是什么。
难度:中等 | 函数式入门必考 | 闭包与参数复用