看段简单的代码:
JS
function curry(fn) {
// curry 函数接收一个原始函数 fn 作为参数
return function curried(...args) {
// curried 是被 curry 函数返回的"柯里化"后的函数。
// 每次调用 curried 时,它都会收集当前传入的参数 args。
// 1. 判断是否收集到足够的参数
// fn.length 是原始函数 fn 期望的参数个数(即函数的"元数"或"arity")。
// args.length 是当前已经收集到的参数个数。
if (args.length >= fn.length) {
// 如果当前收集到的参数数量 (args.length) 大于或等于
// 原始函数 fn 期望的参数数量 (fn.length),
// 说明我们已经收集到了足够的参数来执行原始函数 fn。
// 使用 apply 方法调用原始函数 fn。
// apply 的第一个参数是 this 的上下文,这里我们保持调用时的 this。
// 第二个参数是一个数组,包含了所有收集到的参数 args。
return fn.apply(this, args);
} else {
// 2. 参数不足,返回一个新的函数继续收集参数
// 如果当前收集到的参数数量不足以执行原始函数 fn,
// 那么 curried 函数会返回一个新的函数。
return function(...args2) {
// 这个新函数接收额外的参数 args2。
// 它会递归地调用 curried 函数本身。
// 使用 args.concat(args2) 将之前收集的参数 (args)
// 和当前新传入的参数 (args2) 合并成一个新的参数数组。
// 然后,将这个合并后的参数数组传递给 curried 函数进行下一次判断。
// 同样,使用 apply 保持 this 上下文。
return curried.apply(this, args.concat(args2));
}
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
逐步解析 curriedSum 的调用过程:
假设 sum 函数的 fn.length 是 3。
-
const curriedSum = curry(sum);curry(sum)被调用。- 它返回一个名为
curried的内部函数。curriedSum现在就是这个curried函数。 - 此时,
curried函数内部的闭包记住了fn就是sum。
-
curriedSum(1)curried函数被调用,args是 ``。args.length(1)< fn.length(3),条件if (args.length >= fn.length)不满足。- 进入
else分支。 - 返回一个新的匿名函数。这个匿名函数内部的闭包记住了当前的
args是 ``。 - 所以
curriedSum(1)的结果是一个新的函数。
-
curriedSum(1)(2)- 上一步返回的匿名函数被调用,
args2是 ``。 - 这个匿名函数执行
curried.apply(this, args.concat(args2))。 args(来自上一个闭包) 是,`args2` 是。args.concat(args2)结果是 ``。- 现在
curried函数被递归调用,传入的args是 ``。 - 在这次
curried调用中,args.length(2)< fn.length(3),条件不满足。 - 再次进入
else分支。 - 返回一个新的匿名函数。这个匿名函数内部的闭包记住了当前的
args是 ``。 - 所以
curriedSum(1)(2)的结果又是一个新的函数。
- 上一步返回的匿名函数被调用,
-
curriedSum(1)(2)(3)- 上一步返回的匿名函数被调用,
args2是 ``。 - 这个匿名函数执行
curried.apply(this, args.concat(args2))。 args(来自上一个闭包) 是,`args2` 是。args.concat(args2)结果是 ``。- 现在
curried函数被递归调用,传入的args是 ``。 - 在这次
curried调用中,args.length(3)>= fn.length(3),条件满足! - 进入
if分支。 - 执行
fn.apply(this, args),即sum.apply(this,)。 sum(1, 2, 3)返回6。- 所以
curriedSum(1)(2)(3)的结果是6。
- 上一步返回的匿名函数被调用,
这种柯里化函数的特点和优点:
-
灵活性 :它不仅支持链式调用(每次一个参数),也支持一次性提供所有参数,或者分批提供参数,例如
curriedSum(1, 2, 3)或curriedSum(1, 2)(3)。 -
参数复用(Partial Application) :你可以创建一个预先填充了部分参数的新函数。例如:
jsconst addOneAndTwo = curriedSum(1, 2); // addOneAndTwo 现在是一个等待第三个参数的函数 console.log(addOneAndTwo(3)); // 输出 6 (1 + 2 + 3) console.log(addOneAndTwo(10)); // 输出 13 (1 + 2 + 10) -
延迟执行 :原始函数
fn不会立即执行,直到所有必需的参数都已提供。 -
函数组合:柯里化函数在函数式编程中非常有用,因为它使得函数的组合变得更加容易和自然。
涉及的关键概念:
- 闭包(Closure) :
curried函数以及它内部返回的匿名函数都形成了闭包。它们记住了自己被创建时的环境(例如fn,以及每次调用时累积的args),即使外部函数已经执行完毕,这些内部函数仍然可以访问和操作这些变量。 fn.length:这是JavaScript函数的一个内置属性,表示函数声明时期望的参数个数。apply():用于调用函数,并允许你以数组的形式传递参数,同时可以指定this的上下文。- 递归(Recursion) :
curried函数在else分支中递归地调用了自身,这是实现参数累积和链式调用的关键。 this上下文 :代码中使用了this,这意味着柯里化后的函数在调用时会保留其this上下文。在严格模式下,如果函数是作为独立函数调用,this会是undefined;如果作为对象方法调用,this会指向该对象。在非严格模式下,独立函数调用时this会指向全局对象(浏览器中的window)。