JavaScript 柯里化:把“大餐”拆成“小炒”的艺术

🍛 JavaScript 柯里化:把"大餐"拆成"小炒"的艺术

在函数式编程中,柯里化(Currying) 是一个高频词汇。

很多初学者看到类似 add(1)(2)(3) 这样的代码时会一头雾水:为什么函数可以这样调用?它到底有什么用?

别急,今天我们就把这个看似复杂的概念,嚼碎了喂给你。

📂 目录

  1. [🤔 什么是柯里化?](#🤔 什么是柯里化?)
  2. [🥘 生活化比喻:从"满汉全席"到"自助套餐"](#🥘 生活化比喻:从“满汉全席”到“自助套餐”)
  3. [💻 代码对比:普通函数 vs 柯里化函数](#💻 代码对比:普通函数 vs 柯里化函数)
  4. [🛠️ 手写一个通用的柯里化工具函数](#🛠️ 手写一个通用的柯里化工具函数)
  5. [🚀 柯里化的三大实战场景](#🚀 柯里化的三大实战场景)
  6. [💡 总结](#💡 总结)

1. 🤔 什么是柯里化?

官方定义

柯里化(Currying)是把接受多个参数 的函数,变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

通俗解释

原本你需要一次性给函数所有参数,它才干活。

柯里化之后,你可以分批给参数。每给一个参数,它就返回一个新函数,等着接收下一个参数,直到所有参数都给齐了,它才最终执行并返回结果。

核心特征

  1. 参数分批传递fn(a, b, c) 变成 fn(a)(b)(c)
  2. 延迟执行:参数没给够之前,不执行具体逻辑,只返回新函数。
  3. 参数复用:固定的参数可以先传进去,生成一个"半成品"函数供后续使用。

2. 🥘 生活化比喻:从"满汉全席"到"自助套餐"

想象你去餐厅吃饭:

  • 普通函数

    你必须一次性点完所有菜(主食、饮料、甜点),厨房才开始做。如果你忘了点饮料,就得重新下单,或者等下次再来。

    cook("牛排", "可乐", "冰淇淋")

  • 柯里化函数

    你采用自助餐模式

    1. 你先拿了一个盘子,放了牛排 。(返回一个新状态:盘子里有牛排
    2. 接着,你又拿了可乐 。(返回一个新状态:盘子里有牛排+可乐
    3. 最后,你拿了冰淇淋。(此时盘子满了,厨房开始制作套餐,端给你。)

    cook("牛排")("可乐")("冰淇淋")

好处是什么?

如果你每天都吃牛排,你可以先预置一个"牛排盘",每天只需要决定加什么饮料和甜点即可。这就是"参数复用"!


3. 💻 代码对比:普通函数 vs 柯里化函数

假设我们要实现一个简单的加法函数。

❌ 普通函数

javascript 复制代码
function add(x, y, z) {
  return x + y + z;
}

// 必须一次性传入所有参数
console.log(add(1, 2, 3)); // 6

✅ 柯里化函数

javascript 复制代码
function curriedAdd(x) {
  return function (y) {
    return function (z) {
      return x + y + z;
    };
  };
}

// 可以分批传入参数
const step1 = curriedAdd(1);   // 返回一个函数,等待接收 y
const step2 = step1(2);        // 返回一个函数,等待接收 z
const result = step2(3);       // 所有参数齐了,执行计算,返回 6

// 或者链式调用
console.log(curriedAdd(1)(2)(3)); // 6

看起来代码变多了?是的,手动写柯里化很麻烦。所以我们需要一个通用的工具函数来自动完成这个转换。


4. 🛠️ 手写一个通用的柯里化工具函数

在实际开发中,我们不会为每个函数都手动嵌套闭包。我们会编写一个高阶函数 curry,它能把任何普通函数转换成柯里化函数。

💻 实现代码

javascript 复制代码
/**
 * 通用柯里化函数
 * @param {Function} fn - 需要被柯里化的原函数
 * @returns {Function} - 柯里化后的新函数
 */
function curry(fn) {
  // 获取原函数期望的参数个数
  const arity = fn.length;

  return function judge(...args) {
    // 如果当前收集的参数个数 >= 原函数期望的参数个数
    if (args.length >= arity) {
      // 执行原函数,返回结果
      return fn.apply(null, args);
    } else {
      // 否则,返回一个新函数,继续收集剩余参数
      return function (...restArgs) {
        // 递归调用 judge,将已收集的参数和新参数合并
        return judge.apply(null, args.concat(restArgs));
      };
    }
  };
}

🧪 测试一下

javascript 复制代码
// 1. 定义一个普通函数
function sum(a, b, c) {
  return a + b + c;
}

// 2. 将其柯里化
const curriedSum = curry(sum);

// 3. 各种调用方式均有效
console.log(curriedSum(1, 2, 3));   // 6 (一次性传完)
console.log(curriedSum(1)(2, 3));   // 6 (分批传)
console.log(curriedSum(1)(2)(3));   // 6 (完全柯里化)

// 4. 参数复用示例
const add10 = curriedSum(10); 
console.log(add10(20, 30)); // 60 (10 + 20 + 30)
console.log(add10(1, 2));   // 13 (10 + 1 + 2)

5. 🚀 柯里化的三大实战场景

柯里化不仅仅是炫技,它在实际开发中有巨大的价值。

场景一:参数复用(固定配置)

这是柯里化最核心的用途。当你有一个函数,其中某些参数是固定的,只有少数参数变化时,柯里化可以帮你创建一个"专用版本"的函数。

例子:正则验证

javascript 复制代码
// 普通写法:每次都要传正则表达式
function check(reg, txt) {
  return reg.test(txt);
}

check(/\d+/g, 'test');       // false
check(/[a-z]+/g, 'test');    // true

// 柯里化写法:预先固定正则,生成专用验证器
const curriedCheck = curry(check);

const hasNumber = curriedCheck(/\d+/g);
const hasLetter = curriedCheck(/[a-z]+/g);

// 后续使用极其简洁,且语义清晰
hasNumber('test123');  // true
hasLetter('123');      // false

场景二:延迟执行

在某些场景下,我们希望函数先接收部分参数,但不立即执行,直到满足特定条件或收集完所有参数后再执行。

例子:日志打印

javascript 复制代码
function log(level, date, msg) {
  console.log(`[${level}] ${date}: ${msg}`);
}

const curriedLog = curry(log);

// 预设级别和时间
const infoLog = curriedLog('INFO')(new Date().toLocaleDateString());

// 在代码的不同地方,只关心消息内容
infoLog('用户登录成功');
infoLog('数据加载完成');

场景三:兼容性处理与函数组合

在函数式编程库(如 Lodash、Ramda)中,柯里化是函数组合(Compose)的基础。只有当函数都是单参数(或柯里化后表现为单参数流)时,才能像管道一样轻松串联。

javascript 复制代码
// 假设我们有 lodash 的 curry 和 flow
import { curry, flow } from 'lodash';

const add = curry((a, b) => a + b);
const multiply = curry((a, b) => a * b);

// 组合函数:先加 10,再乘 2
const add10ThenMultiply2 = flow(add(10), multiply(2));

console.log(add10ThenMultiply2(5)); // (5 + 10) * 2 = 30

💡 总结

特性 普通函数 柯里化函数
参数传递 一次性传递所有参数 分批传递,一次一个(或一组)
执行时机 调用即执行 参数凑齐后才执行(延迟执行)
主要优势 简单直观 参数复用延迟执行易于组合
适用场景 大多数常规业务逻辑 配置项固定、函数式编程、高阶组件封装

🚀 博主寄语

柯里化本质上是一种**"降维打击"**的思维:

它将一个多参问题,分解为多个单参问题的序列。

不要为了柯里化而柯里化。
当发现你在重复传递相同的参数时,就是使用柯里化的最佳时机。

记住口诀

多参变单参,

闭包来帮忙。

参数若不够,

返回新函数。

参数若凑齐,

执行出结果。

复用与延迟,

代码更优雅。

希望这篇文档能帮你彻底掌握柯里化!如果有疑问,欢迎在评论区留言。👇

喜欢这篇文章吗?记得点赞、收藏、转发哦! ❤️

相关推荐
每天吃饭的羊1 小时前
JSZip的使用
开发语言·javascript
qq_589568102 小时前
java基础学习,案例练习,即时通讯
java·开发语言·学习
DevilSeagull2 小时前
Windows 批处理 (Batch) 编程: 从入门到入土. (一) 基础概念与环境配置
开发语言·windows·后端·batch·语言
AI科技星2 小时前
全域数学·第卷:场计算机卷(场空间计算机)【乖乖数学】
java·开发语言·人工智能·算法·机器学习·数学建模·数据挖掘
charlie1145141912 小时前
嵌入式C++实践开发第21篇(单片机实践):按钮输入 —— 硬件原理、消抖与HAL API
开发语言·c++·单片机
前端老石人2 小时前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
0xDevNull2 小时前
Java泛型详解
java·开发语言·后端
yeeanna2 小时前
GO函数的特殊性
开发语言·后端·golang