告别 add(1, 2)!通过 JS 柯里化,让你的代码更加优雅

在此之前的很多次面试里,你可能都遇到过这样一个经典的"送命题":

"请实现一个 add 函数,使得 add(1)(2)(3) 的结果等于 6。"

乍一看,这像是面试官在故意刁难。毕竟在正常的业务开发里,谁没事会把参数拆得七零八落?但其实,这背后藏着一个函数式编程中非常性感且实用的概念------柯里化 (Currying)

今天我们就抛开那些晦涩的数学定义,用大白话和最骚的代码,彻底搞懂柯里化到底是个什么鬼,以及它为什么能让你的代码更优雅。


🧐 什么是柯里化?从"一口闷"到"细嚼慢咽"

在传统的函数调用中,我们习惯"一口闷"。

比如一个求和函数 function add(a, b, c) { return a + b + c },我们要么一次性把 a, b, c 全传进去,要么就报错。

柯里化的本质,就是把"一次性传参"变成"一个个传参"

如果说普通函数是 $f(a, b, c)$,那么柯里化后的函数就是 $f(a)(b)(c)$

核心心法:闭包是仓库,递归是搬运工

柯里化之所以能实现,全靠 JavaScript 的两个老朋友:

  1. 闭包 (Closure) :每一层函数都像一个"小仓库",记住了之前传进来的参数,这些变量(自由变量)不会被销毁,而是静静地待在那里。
  2. 递归 (Recursion) :只要参数还没凑够,我就继续返回一个新的函数,等着你传下一个参数;一旦凑够了,直接"召唤神龙",执行原函数。

🛠 手撸一个通用的 Curry 函数

光说不练假把式。我们来看看,如何把任何一个普通函数,变成柯里化函数。

1. 知道什么时候"够了"

首先,我们需要知道原函数到底需要几个参数。在 JS 里,函数有一个属性 length,它代表了函数形参的个数。

JavaScript

javascript 复制代码
function add(a, b, c, d) {
    return a + b + c + d;
}
console.log(add.length); // 输出 4 -> 这就是我们的目标数量

2. 实现核心逻辑

接下来,我们写一个 curry 辅助函数。它的逻辑非常简单粗暴:参数没收齐,我就一直返回函数;收齐了,我就执行。

JavaScript

php 复制代码
/**
 * 柯里化通用实现
 * @param {Function} fn - 需要被柯里化的原函数
 */
function curry(fn) {
    // 这里的 ...args 是闭包这层仓库里已经存好的参数
    return function curried(...args) {
        
        // 【核心判断】:当前收集的参数数量 >= 原函数需要的参数数量?
        if (args.length >= fn.length) {
            // 🚪 退出条件:参数够了,直接执行原函数
            return fn(...args);
        } else {
            // 🔄 递归继续:参数不够?返回一个新的函数继续收参数
            return (...rest) => {
                // 将之前存的 args 和新传进来的 rest 合并,递归调用 curried
                return curried(...args, ...rest);
            };
        }
    }
}

3. 见证奇迹的时刻

现在我们用这个 curry 函数来包装一下我们的 add

JavaScript

scss 复制代码
const curriedAdd = curry(add);

// 玩法 1:标准柯里化,一次传一个
console.log(curriedAdd(1)(2)(3)(4)); // 10

// 玩法 2:不严谨柯里化,一次传多个(更灵活!)
console.log(curriedAdd(1, 2)(3)(4)); // 10
console.log(curriedAdd(1, 2, 3, 4)); // 10

你看,无论你怎么传,只要总数凑够了 4 个,它就会给你吐出结果。这就是闭包在背后默默负重前行的结果。


💡 为什么我要用它?(实战场景)

你可能会问:"这就这就?除了面试装X,这有啥用?"

柯里化最大的价值在于:参数复用(偏函数应用)与 延迟执行。

场景:打造优雅的日志系统

假设你有一个通用的日志打印函数:

JavaScript

typescript 复制代码
// 通用日志函数
const log = (type, message) => {
    console.log(`[${type}]: ${message}`);
}

在实际业务中,你要打印很多 ERROR 类型的日志。如果不柯里化,你得这么写:

JavaScript

arduino 复制代码
log("ERROR", "接口挂了");
log("ERROR", "数据不对");
log("ERROR", "服务器着火了");

太 啰 嗦 了! ERROR 这个参数我们重复写了三遍。

利用柯里化(或者在这里更准确说是偏函数应用的思想),我们可以"固定"住第一个参数:

JavaScript

typescript 复制代码
// 这里我们用箭头函数简写一个针对 log 的柯里化版本
const curriedLog = type => message => {
    console.log(`[${type}]: ${message}`);
}

// 🏭 批量生产专用函数
const errorLog = curriedLog("ERROR"); // 固定住了 "ERROR"
const infoLog = curriedLog("INFO");   // 固定住了 "INFO"

// ✅ 现在的调用:语义更清晰,代码更简洁
errorLog("接口挂了");      // [ERROR]: 接口挂了
infoLog("页面加载完成");    // [INFO]: 页面加载完成

这一波操作下来,代码的可读性(errorLog 一眼就知道是干嘛的)和复用性(不用重复传 type)都得到了极大的提升。


📝 总结一下

  1. 柯里化 (Currying) 是一种将多参数函数转换为一系列单参数函数的技术。
  2. 实现原理 :利用 闭包 保存历史参数,利用 递归 收集参数,利用 fn.length 判断退出时机。
  3. 应用场景:当你需要频繁调用一个函数,且其中某些参数是固定的时候,柯里化能帮你通过"预置参数"来减少重复代码,提升代码的语义化。

下次再看到 add(1)(2)(3),别再觉得它是花拳绣腿了。它是 JavaScript 函数式编程中一把精致的手术刀,用得好,你的代码就能像诗一样优雅。✨

相关推荐
ohyeah2 小时前
柯理化(Currying):让函数参数一个一个传递
前端·javascript
CryptoRzz2 小时前
StockTV API 对接全攻略(股票、期货、IPO)
java·javascript·git·web3·区块链·github
w139548564222 小时前
Flutter跨平台开发鸿蒙化JS-Dart通信桥接组件使用指南
javascript·flutter·harmonyos
Hilaku2 小时前
当 Gemini 3 能写出完美 CSS 时,前端工程师剩下的核心竞争力是什么?
前端·javascript·ai编程
小兵张健3 小时前
森果云面试经历
面试
DigitalOcean3 小时前
加速 JavaScript 开发:DigitalOcean 应用托管现已原生支持 Bun
javascript
零雲3 小时前
Java面试:@Component和@Bean的区别是什么
java·开发语言·面试
用户6802659051194 小时前
如何利用 Endpoint Central 提高企业终端管理效率
javascript·后端·面试
咖啡の猫4 小时前
TypeScript 开发环境搭建
前端·javascript·typescript