在 JavaScript 中,函数是一等公民,不仅可以被调用、赋值,还可以作为参数传递或返回值。这种灵活性使得我们可以对函数进行各种变换和组合,其中"柯理化"(Currying)是一种非常实用的函数式编程技巧。本文将从基础示例出发,逐步深入到通用柯理化函数的实现,并结合实际应用场景说明其价值。
从普通函数说起
我们先来看一个最简单的加法函数:
javascript
function add(a, b) {
return a + b
}
console.log(add(1, 2)) // 输出: 3
这是一个典型的二元函数,接收两个参数并返回它们的和。但在某些场景下,我们可能希望分步传参:先传入第一个参数,稍后再传入第二个。这在函数式编程中非常常见,尤其是在需要复用部分逻辑时。
手动实现柯理化
为了支持分步传参,我们可以手动将 add 函数改写为嵌套函数的形式:
scss
function add(a) {
return function(b) {
return a + b
}
}
add(1)(2) // 输出: 3
这种方式被称为函数柯理化。它将原本接收多个参数的函数,转换为一系列只接收一个参数的函数。每一步返回一个新的函数,直到收集完所有参数后才真正执行原始逻辑。
这种写法虽然有效,但存在明显的问题:每次都要手动重写函数结构。如果函数有三个、四个甚至更多参数,代码会变得冗长且难以维护。因此,我们需要一个通用的解决方案。
通用柯理化函数
JavaScript 中的函数本身也是对象,拥有许多内置属性,其中 length 属性表示函数在定义时声明的形参数量(不包括剩余参数)。利用这一特性,我们可以编写一个通用的 curry 函数:
javascript
function add(a, b) {
return a + b
}
// 函数也是对象,函数的 length 属性能够帮我们拿到定义时参数的数量
console.log(add.length) // 输出: 2
函数是'一等公民',其length属性能够帮助我们拿到定义时参数的个数,基于fn.length,我们可以判断当前已传入的参数是否足够执行原函数。以下是通用柯理化函数的实现:
scss
function curry(fn) {
// closure curry fn此时是自由变量 在闭包中
// ...args 收集参数 非严格柯理化函数
return function curried(...args) {
if (args.length >= fn.length) {
// >= 而不是 === 说明支持多传参数(但是add只用前两个)
// 这是非严格柯理化的关键:允许一次传多个参数
return fn(...args)
}
// 否则 返回一个新的函数 接收下一批参数...rest
// 然后递归调用curried 把已收集的args和新传入的rest合并后继续传递
return (...rest) => curried(...args, ...rest)
}
}
const curriedAdd = curry(add)
curriedAdd(1)(2) // 输出: 3
这个 curry 函数的核心思想是:
- 闭包保存原始函数
fn和已收集的参数; - 每次调用返回的新函数都检查当前参数总数是否达到
fn.length; - 如果满足条件,立即执行
fn;否则继续返回一个接收更多参数的函数; - 使用
>=而非===,使得用户可以一次性传入全部参数(如curriedAdd(1, 2)),也可以分多次传入(如curriedAdd(1)(2)),这种设计称为非严格柯理化,更加灵活实用。
实际应用:日志函数的参数固定
柯理化的真正价值不仅在于语法上的优雅,更在于提升代码的语义性和复用性。一个典型的应用场景是日志记录。
考虑以下日志函数:
typescript
// 日志函数
const log = type => message => {
console.log(`[${type}]: ${message}`)
}
// 箭头函数嵌套
// 闭包
// 柯理化用于固定函数参数
// 固定日志类型 提升函数语义
const errorLog = log('error')
const infoLog = log('info')
errorLog('接口异常') // 输出: [error]: 接口异常
infoLog('页面加载完成') // 输出: [info]: 页面加载完成
这里,log 是一个柯理化函数:第一次调用传入日志类型(如 'error' 或 'info'),返回一个专门用于该类型的日志函数。这样做的好处非常明显:
- 语义清晰 :
errorLog和infoLog的用途一目了然; - 减少重复 :无需每次都写
log('error', '...'); - 易于维护 :如果未来要修改日志格式,只需调整
log函数即可,所有派生函数自动生效。
这种"固定部分参数以生成专用函数"的模式,在配置、工具函数封装、事件处理等场景中极为常见。
总结
柯理化是一种将多参数函数转换为一系列单参数函数的技术。它通过闭包保存中间状态,利用函数的 length 属性判断参数是否收集完毕,从而实现灵活的参数传递方式。
在实际开发中,柯理化不仅能简化函数调用,还能通过固定部分参数 来创建语义明确的专用函数,提升代码的可读性与可维护性。无论是手动编写嵌套函数,还是使用通用 curry 工具函数,掌握这一技巧都能让我们在函数式编程的道路上走得更远。
虽然现代 JavaScript 生态中已有 Lodash 等库提供 _.curry 方法,但理解其内部原理,有助于我们在没有依赖的情况下灵活应对各种场景。正如我们在日志函数中所见,柯理化不仅是理论概念,更是提升工程实践的有效手段。
下次当你发现自己反复传入相同的参数时,不妨考虑:是否可以通过柯理化,将这些重复的部分"固化"下来,让代码更简洁、更富有表达力?