告别 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 函数式编程中一把精致的手术刀,用得好,你的代码就能像诗一样优雅。✨

相关推荐
xj7573065335 分钟前
Django 面试常见问题
python·面试·django
a努力。5 分钟前
得物Java面试被问:Netty的ByteBuf引用计数和内存释放
java·开发语言·分布式·python·面试·职场和发展
CTO Plus技术服务中6 分钟前
大厂面试笔记和参考答案!浏览器自动化、自动化测试、自动化运维与开发、办公自动化
运维·笔记·面试
pas13613 分钟前
33-mini-vue 更新element的children-双端对比diff算法
javascript·vue.js·算法
xiaoxue..16 分钟前
Nest.js 框架 企业级开发通关手册
面试·typescript·node.js·开发框架·nest.js
源代码•宸18 分钟前
大厂技术岗面试之一面(准备自我介绍、反问)
经验分享·后端·算法·面试·职场和发展·golang·反问
靓仔建21 分钟前
用tdesign-vue-next的t-tree-select做个下拉单选框
javascript·vue.js·tdesign
oscar9997 小时前
软件测试面试全攻略之初级篇
软件测试·面试·职场和发展·初级篇
美酒没故事°10 小时前
vue3拖拽+粘贴的综合上传器
前端·javascript·typescript
码农水水11 小时前
蚂蚁Java面试被问:混沌工程在分布式系统中的应用
java·linux·开发语言·面试·职场和发展·php