看段简单的代码:
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
)。