函数柯里化
基础知识
什么是函数柯里化 (Currying)? 柯里化是一种将一个接收多个参数的函数,转变为一系列接收单一参数(或部分参数)的函数的技术。每当调用这些新函数并传入一个参数后,它会返回一个接收下一个参数的新函数,直到所有参数都集齐,最后才会执行原始函数并返回最终结果。
简单来说,柯里化就是一种“参数的延迟接收”和“函数的预加载”技术。
// 普通函数 function add(a, b, c) { return a + b + c; } add(1, 2, 3); // 6 // 柯里化后的函数 let curriedAdd = curry(add); curriedAdd(1)(2)(3); // 6
柯里化的优点
- 参数复用: 我们可以通过预先提供一部分参数,来创建一个功能更具体、更专业的新函数。
- 提前确认/延迟执行: 我们可以不断地收集和准备参数,将真正的计算推迟到所有参数都准备就绪时再执行。
- 提高函数通用性和组合性: 柯里化后的函数粒度更小,更容易进行函数组合(Function Composition)。
应用场景
- 创建偏函数 (Partial Application): 例如,创建一个
add5
函数,它总是将传入的数字加 5。 - 代码组织与复用: 在函数式编程中,柯里化是实现高阶函数和组合思想的基础。
- 延迟计算: 在需要进行一系列配置,最后才执行的场景中非常有用。
- 创建偏函数 (Partial Application): 例如,创建一个
核心思路
我们的目标是创建一个高阶函数 curry(func)
,它接收一个普通函数 func
作为参数,并返回一个柯里化后的新函数。这个新函数需要具备以下能力:
参数收集器: 柯里化函数的核心是一个“参数收集器”。它需要一个地方(通常是一个数组)来存放历次调用时传入的参数。
闭包的应用:
curry
函数返回的新函数,必须通过闭包来“记住”两个关键信息:- 原始的目标函数
func
。 - 已经收集到的参数列表
args
。
- 原始的目标函数
递归与返回: 返回的柯里化函数在被调用时,需要做出判断:
- 参数够了吗?: 检查当前收集到的参数数量,是否已经达到了原始函数
func
所需的参数数量(可以通过func.length
获取)。 - 如果参数还不够: 将新传入的参数追加到已收集的参数列表中,然后返回一个新的、同样具备柯里化能力的函数。这个新函数会继续这个收集过程。这是一个递归的思路。
- 如果参数已经足够: 将所有收集到的参数,一次性地传递给原始函数
func
并执行它,然后返回最终的计算结果。这是递归的出口。
- 参数够了吗?: 检查当前收集到的参数数量,是否已经达到了原始函数
代码实现
下面是一个通用性很强的 curry
函数实现。
/**
* 实现一个通用的函数柯里化函数
* @param {Function} func - 需要被柯里化的原始函数
* @returns {Function} - 返回一个柯里化后的新函数
*/
function curry(func) {
// 1. curry 函数返回一个新的函数(柯里化核心函数)
// ...initialArgs 是首次可能传入的预设参数
return function curried(...initialArgs) {
// 2. 检查已收集的参数数量是否达到了原始函数所需的数量
if (initialArgs.length >= func.length) {
// 3. 如果参数足够,则直接执行原始函数并返回结果
return func.apply(this, initialArgs);
} else {
// 4. 如果参数不够,则返回一个新的函数,继续收集参数
return function (...nextArgs) {
// 5. 递归调用 curried,将旧参数和新参数合并后传入
return curried.apply(this, [...initialArgs, ...nextArgs]);
};
}
};
}
代码解析
高阶函数结构:
curry(func)
接收目标函数func
,返回柯里化函数curried
,体现高阶函数特性(函数返回函数)。curried
函数通过 rest 参数...initialArgs
收集所有传入参数,支持分步传参。
参数数量判断:
- 核心条件
if (initialArgs.length >= func.length)
决定是否执行原始函数。 func.length
属性表示函数定义时期望的参数个数(不含 rest 参数),例如function(a, b){}
的length
为 2。- 通过比较已收集参数
initialArgs
的长度与func.length
,判断参数是否足够。
- 核心条件
执行原始函数:
- 当参数足够时,通过
return func.apply(this, initialArgs)
执行原始函数。 - 使用
apply
确保:- 正确绑定
this
上下文。 - 以数组形式传递所有收集到的参数。
- 正确绑定
- 当参数足够时,通过
返回新函数(继续收集):
- 当参数不足时,返回匿名函数
function(...nextArgs) { ... }
。 - 新函数负责接收后续调用传入的参数
nextArgs
,继续收集参数直至满足条件。
- 当参数不足时,返回匿名函数
递归调用与参数合并:
- 核心逻辑
return curried.apply(this, [...initialArgs, ...nextArgs])
通过递归实现参数累积。 - 递归调用
curried
函数自身,通过apply
合并前后参数:[...initialArgs, ...nextArgs]
将历史参数与新参数合并为新数组。- 递归持续至合并参数长度满足
func.length
,最终执行原始函数。
- 此机制实现了分步传参和延迟执行的柯里化特性。
- 核心逻辑
使用示例
// 准备一个多参数函数
function multiply(a, b, c, d) {
return a * b * c * d;
}
// 柯里化
const curriedMultiply = curry(multiply);
// 各种调用方式
console.log(curriedMultiply(2)(3)(4)(5)); // 输出: 120
console.log(curriedMultiply(2, 3)(4, 5)); // 输出: 120
console.log(curriedMultiply(2, 3, 4)(5)); // 输出: 120
// 参数复用:创建一个“乘以6”的函数
const multiplyBy6 = curriedMultiply(2, 3);
console.log(multiplyBy6(4, 5)); // 输出: 120
console.log(multiplyBy6(10, 10)); // 输出: 600