🌟柯里化(Curry):让函数更灵活、更聪明的编程技巧
一句话记住:柯里化 = 把一个需要多个参数的函数,变成可以“分步传参”的函数。
在 JavaScript 函数式编程中,柯里化(Currying) 是一个既优雅又实用的核心概念。它不仅能提升代码复用性,还能让逻辑更清晰、组合更自然。本文将结合三个小例子带你从“是什么”到“怎么写”再到“为什么用”,彻底掌握柯里化。
一、什么是柯里化?
假设你想写一个加法函数,把两个数加起来:
// 普通写法
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 输出:5
这很直接,对吧?但如果我们不能一次拿到两个数呢?
比如:
- 先知道第一个数是
2 - 过一会儿才知道第二个数是
3
这时候,普通函数就不好用了。于是,我们可以把它“改造”一下:
function add(a) {
return function(b) {
return a + b;
};
}
// 使用方式变了!
const add2 = add(2); // 先记住“加2”
console.log(add2(3)); // 再加3 → 输出:5
// 也可以连着写
console.log(add(2)(3)); // 同样输出:5
🔍 这就是最基础的“柯里化”!
- 原本:
add(2, 3) - 柯里化后:
add(2)(3)
💡 核心思想:
先接收一部分参数(比如a),返回一个新函数;这个新函数再接收剩下的参数(比如b),最后完成计算。
这就叫 “分步传参” 。
二、怎么手写一个通用的柯里化函数?
上面的例子只处理两个参数。现实中,函数可能有 3 个、5 个甚至更多参数。难道我们要手动写 return function(c) { return function(d) {...} } 吗?太麻烦了!
于是,我们需要一个万能工具:自动把任意函数变成柯里化版本!
// 假设我们有一个四参数函数
function multiply(a, b, c, d) {
return a * b * c * d;
}
// 手写一个通用的 curry 函数
function curry(fn) {
return function curried(...args) {
// 如果当前传入的参数数量 >= 原函数需要的参数数量
if (args.length >= fn.length) {
return fn(...args); // 参数齐了,直接执行!
}
// 如果还不够,就返回一个新函数,继续收参数
return function(...nextArgs) {
return curried(...args, ...nextArgs);
};
};
}
// 使用
const curriedMultiply = curry(multiply);
// 以下所有写法都有效!
console.log(curriedMultiply(1)(2)(3)(4)); // 24
console.log(curriedMultiply(1, 2)(3, 4)); // 24
console.log(curriedMultiply(1)(2, 3, 4)); // 24
console.log(curriedMultiply(1, 2, 3, 4)); // 24
🧠 它是怎么工作的?(像讲故事一样)
想象你在点一份“四层蛋糕”:
- 第一次你告诉店员:“我要第一层(1)” → 店员记下,但不给你蛋糕,而是说:“请继续告诉我下一层。”
- 第二次你说:“第二、三层是 2 和 3” → 店员现在有 [1,2,3],还是不够,继续等。
- 第三次你说:“第四层是 4!” → 现在齐了!店员立刻把蛋糕做好给你。
curry 函数就像这个聪明的店员:
- 它用 闭包 记住你之前说的所有层数(参数)
- 每次你给新层数,它就合并起来
- 一旦层数够了(
args.length >= fn.length),就立刻做蛋糕(执行原函数)
✅ 关键技巧:递归 + 闭包 + 剩余参数(
...args)
三、柯里化到底有什么用?—— 看真实场景
光会写还不够,得知道为什么用。来看一个前端开发中超级常见的需求:打日志。
❌ 普通写法(重复、易错):
console.log("[ERROR]: 用户未登录");
console.log("[INFO]: 页面加载完成");
console.log("[WARN]: 图片加载慢");
console.log("[ERROR]: 接口超时");
问题:
- 每次都要手写
[ERROR]、[INFO],容易拼错 - 代码冗长,不好维护
✅ 用柯里化改造:
const log = type => message => {
console.log(`[${type}]: ${message}`);
};
// 提前“配置”好日志类型
const errorLog = log("ERROR");
const infoLog = log("INFO");
const warnLog = log("WARN");
// 使用时只需传消息!
errorLog("用户未登录"); // [ERROR]: 用户未登录
infoLog("页面加载完成"); // [INFO]: 页面加载完成
warnLog("图片加载慢"); // [WARN]: 图片加载慢
🌟 这样做的好处:
| 好处 | 说明 |
|---|---|
| 语义清晰 | errorLog("xxx") 一看就知道是错误日志 |
| 避免重复 | 不用每次都写 "ERROR" |
| 易于复用 | 一个 errorLog 可以用在项目任何地方 |
| 方便组合 | 可以直接传给 forEach: errors.forEach(errorLog) |
💡 这就像你有三支专用笔:红笔写错误,蓝笔写信息,黄笔写警告——分工明确,效率翻倍!
最后:常见疑问解答
❓ 问:箭头函数 a => b => ... 是什么意思?
答:这是简写!等价于:
function(a) {
return function(b) {
...
};
}
❓ 问:fn.length 是什么?
答:它是函数定义时的形参个数。比如:
function f(a, b, c) {}
console.log(f.length); // 3
✅ 总结:柯里化不是炫技,而是工程智慧
🌟 柯里化的本质,是把“一次性配置”和“动态执行”分离,让函数更具弹性与表达力。
虽然Lodash 有 _.curry,Ramda 库默认所有函数都支持柯里化。但理解原理很重要!
在 React Hooks、Redux、Lodash 等现代前端库中,柯里化思想无处不在。掌握它,你就离写出更简洁、更健壮、更函数式的代码更近了一步!
下次当你发现自己在重复传某个固定参数时——就是柯里化登场的时候了!