JavaScript 柯里化完全指南:闭包 + 手写 curry,一步步拆解原理

在 JavaScript 中,函数是开发者手中的多面利器。除了完成基本的功能逻辑,函数还能充当"参数工厂"、"函数生成器"甚至是"状态管理者"。今天我们要聊的,是一个在函数式编程中非常重要的技巧------柯里化(Currying)

很多人第一次看到 add(1)(2)(3) 这种写法可能会一头雾水,这究竟是啥操作?其实这就是柯里化的典型用法。本文将带你从基础概念到源码实现,再到实际应用场景,一步步拆解柯里化的底层原理和它与闭包的关系。


什么是柯里化?

简单来说,柯里化是把一个多参数函数,转换成一系列接收单一参数函数的技术,并在收集完所有参数后执行。

举个例子:

js 复制代码
function add(a, b, c) {
  return a + b + c;
}

// 普通调用
add(1, 2, 3); // 6

// 柯里化后
addCurry(1)(2)(3); // 6

这看起来像是魔法,其实背后全靠函数嵌套 + 闭包保存状态。


柯里化和闭包到底有啥关系?

闭包的定义是:函数可以"记住"并访问它定义时的作用域,即使它在当前词法环境之外执行。

换句话说,外层函数的变量可以被内层函数记住。这正是柯里化能够"逐步记住参数"的关键。

示例:

js 复制代码
function outer(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

outer(1)(2)(3); // 6

每一层函数都"闭住"了上一层的参数值。通过这种方式,参数一个个"被记住",最后一次性用于求值。


手写 curry 函数:核心实现逻辑

我们希望写一个通用的 curry 函数,能把任何多参函数转换成柯里化函数。核心思路如下:

  1. 判断参数是否收集完毕
  2. 如果没收完,就继续返回函数收集剩余参数
  3. 收完了就执行原始函数

实现代码如下:

js 复制代码
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn(...args); // 参数收集完毕,执行原函数
    } else {
      return function (...newArgs) {
        return curried(...args, ...newArgs); // 继续收集参数
      };
    }
  };
}

测试一下:

js 复制代码
function add(a, b, c) {
  return a + b + c;
}

const addCurry = curry(add);

console.log(addCurry(1)(2)(3));       // 6
console.log(addCurry(1, 2)(3));       // 6
console.log(addCurry(1)(2, 3));       // 6

柯里化函数不仅能一个参数一个参数传,也支持一次传多个,非常灵活。


curry 函数拆解讲解

js 复制代码
function curried(...args) {
  if (args.length >= fn.length) {
    return fn(...args);
  }
  return (...newArgs) => curried(...args, ...newArgs);
}
  • fn.length 是原始函数需要的参数个数;
  • args 是目前已经收集到的参数;
  • 如果不够,就返回一个新函数继续收;
  • 最终收齐后再一次性调用 fn(...args) 执行。

通过不断嵌套返回函数,我们可以把一次性的参数收集,变成多次逐步收集------这就是柯里化。


为什么不直接用 arguments?

你可能会问,我用 arguments...args 直接收参数不行吗?

js 复制代码
function add() {
  let result = 0;
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
}

这种写法确实能处理不定参数,但它不是柯里化 ,也不能让你优雅地 add(1)(2)(3)。柯里化强调的是函数返回函数 ,是一种延迟执行 + 收集参数的机制。

拓展阅读:函数对象 & arguments 参数处理机制

在 JavaScript 中,函数本身其实也是对象。每个函数都附带一些属性,比如:

js 复制代码
function add(a, b, c) {}
console.log(add.length); // 输出 3,表示期望的参数个数

这里的 add.length 并不是函数的功能输出,而是函数声明时定义的形参个数,这在我们实现 curry 函数时非常重要 ------ 它告诉我们应该收集多少个参数。


arguments:函数中的参数"总管"

我们可以通过一个例子来看 arguments 是什么:

js 复制代码
function add() {
  console.log(arguments, arguments.length); // [Arguments] { '0': 1, '1': 2, '2': 3 }  3
  console.log(Object.prototype.toString.call(arguments)); // [object Arguments] 
  const args = Array.from(arguments); // 转为真正的数组
  console.log(Object.prototype.toString.call(args)); // [object Array]

  let result = 0;
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  console.log(result);
}

add(1, 2, 3); // 输出 6

🔍 解释几点:

  • arguments 是一个类数组对象 ,它具有 .length 属性,也可以用索引访问每个参数。
  • 但它 不是 真正的数组,不能直接使用 .map().filter() 等数组方法。
  • 我们通常通过 Array.from(arguments)[...arguments](ES6 展开运算符)来将其转化为标准数组。

柯里化的实际应用场景

1. 提前"预处理"参数

js 复制代码
function checkLength(min, max, str) {
  return str.length >= min && str.length <= max;
}

const check6to12 = curry(checkLength)(6, 12);
check6to12('abc');     // false
check6to12('password'); // true

这就像是做了一个"定制版"的校验函数。

2. 函数组合时更自然

js 复制代码
const multiply = curry((a, b) => a * b);
const double = multiply(2);
double(5); // 10

你可以根据场景固定某些参数,获得更语义化的函数。


总结

柯里化不仅是一个函数技巧,更是一种思维方式:通过拆解参数,实现延迟执行与更强的复用性

回顾要点:

  • 柯里化 ≠ 不定参数
  • 它通过闭包逐步收集参数
  • 参数收集完毕才执行原函数
  • 有助于提升代码可读性、组合性与复用性

彩蛋:用一句话记住 curry 原理

"我先记着你这几个参数,等人到齐了我就干活。"


如果你对函数式编程感兴趣,柯里化绝对是一个值得深入实践的核心概念。建议你自己手写几遍 curry 函数,尝试用它封装一些日常函数,你会发现它能带来很多意想不到的写法优化。

相关推荐
孤水寒月1 小时前
基于HTML的悬窗可拖动记事本
前端·css·html
祝余呀1 小时前
html初学者第一天
前端·html
脑袋大大的2 小时前
JavaScript 性能优化实战:减少 DOM 操作引发的重排与重绘
开发语言·javascript·性能优化
速易达网络4 小时前
RuoYi、Vue CLI 和 uni-app 结合构建跨端全家桶方案
javascript·vue.js·低代码
耶啵奶膘4 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
JoJo_Way4 小时前
LeetCode三数之和-js题解
javascript·算法·leetcode
视频砖家4 小时前
移动端Html5播放器按钮变小的问题解决方法
前端·javascript·viewport功能
lyj1689975 小时前
vue-i18n+vscode+vue 多语言使用
前端·vue.js·vscode
小白变怪兽6 小时前
一、react18+项目初始化(vite)
前端·react.js
ai小鬼头6 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github