JavaScript 柯里化原理:闭包与递归的结合

作者:闭包旅人

时间:2026-10-25

浏览:231

本文通过两数相加到多参数求和的演进,解析 JavaScript 中柯里化的核心原理。文章指出,通过闭包保存参数并配合递归调用,可以实现不定参数累加的功能。这种“闭包+递归”的模式不仅符合开闭原则,也是函数式编程中处理副作用和构建管道的重要思想基础。

文章目录

引言

为了清晰阐述概念,我们从一个基础的两数相加场景入手,逐步深入探讨。

在 JavaScript 中,实现两个数字相加的函数通常写法如下:

function sum(a,b){
    console.log(a+b)
}
sum(1,2)

这种基础实现方式在功能上是完全正确的。

然而,随着业务需求的变化,产品经理提出了新的要求:需要支持三个数字相加。

面对这一需求,最直观的做法是在原有函数基础上直接增加一个参数。

修改后的代码可能如下所示:

function sum(a,b,c){
    console.log(a+b+c)
}
sum(1,2,3)

这种修改方式是否存在问题?

答案是肯定的,存在显著缺陷。

这种修改方式同时违反了软件设计中的“开闭原则”和“单一职责原则”。

对于不熟悉设计模式的朋友,这里简要解释这两个概念:

  • “开闭原则”(Open-Closed Principle)指的是在软件系统中,对扩展开放,对修改关闭。即在编程时,应尽量避免直接修改现有的函数、类或模块,而是通过扩展来增加新功能。
  • “单一职责原则”(Single Responsibility Principle)指的是每个函数、类或模块应当只负责一项明确的功能。

在上述修改中,我们改变了 sum 函数的参数结构以及内部调用逻辑,这直接违反了“开闭原则”。

此外,sum 函数原本仅负责两个数字的相加,修改后却承担了三个数字相加的职责,功能边界发生了改变,这违反了“单一职责原则”。

如果严格遵循单一责任原则,代码可能如下所示:

// 负责两数相加
function sum2(a,b){
    console.log(a+b)
}
// 负责三数相加
function sum3(a,b,c){
    console.log(a+b+c)
}

显然,这种写法在实际开发中是不可行的。如果需要支持一万个数字相加,就需要编写一万个不同的函数。

加法的本质逻辑只有一个:逐个累加。无论最终需要相加多少个数值,底层操作始终是“一个加一个”。

因此,我们设想是否存在一种函数:其核心功能是“加法”,但参数数量可变,接收几个参数就执行几次加法操作。

// 负责“加法”,
function addCurry(){
    ...
    ...
    ...
}
addCurry(1)(2) // 两数相加
addCurry(1)(2)(3) // 三数相加
...
addCurry(1)(2)(3)...(n) // n 数相加

这种函数模式正是“柯里化”(Currying)的体现。或者更准确地说,这是柯里化的实现过程或思想。在此处,我们不必过分纠结于严格的学术定义,重点在于理解其机制。

接下来,我们将逐步推导这种函数的构成方式。

为了实现“逐个累加”并存储中间参数,我们需要借助 JavaScript 中的核心机制。

答案就是:闭包(Closure)。

闭包的核心秘密可以概括为四个字:延迟处理。

具体含义如下:

function directHandle(a,b){
    console.log("直接处理",a,b)
}
directHandle(111,222)
// 直接处理 111 222
function delayHandle(a){
    return function(b){
         console.log("延迟处理",a,b)
    }
}
delayHandle(111)
// ƒ (b){
//	    console.log("延迟处理",a,b)
//	}

如 delayHandle(111) 所示,它并不像 directHandle(111,222) 那样直接打印结果,而是返回一个内部函数 f(b)。此时,初始参数 111 被保存在闭包环境中,当调用 delayHandle(111)(222) 时,才能得到最终输出。这种机制体现了延迟处理的思想。

补充说明:

延迟处理是函数式编程的核心特征之一。在无法保证所有函数都是纯函数的情况下,将最终处理推迟到管道操作的最后阶段,可以最大程度地减少副作用。这涉及 Monad(单子)思想,此处不再展开。

回到主题,我们利用闭包来实现最基础的柯里化版本:

// 两数相加
function addCurry(a){
    return function(b){
            console.log(a+b)
    }
}
addCurry(1)(2)
// 三数相加
function addCurry(a){
    return function(b){
        return function(c){
             console.log(a+b+c)
        }
    }
}
addCurry(1)(2)(3)

观察上述代码,编写两个嵌套闭包的过程实际上已经隐含了递归的逻辑。如果继续这样嵌套下去,本质上就是递归调用。

因此,当需要处理 n 个参数时,我们需要进行 n-1 次递归调用 return function。

基于此,addCurry 函数的具体写法如下:

 let arr = []
 function addCurry() {
     let arg = Array.prototype.slice.call(arguments); // 递归获取后续参数
     arr = arr.concat(arg);
      if (arg.length === 0) { // 如果参数为空,则判断递归结束
          return arr.reduce((a,b)=>{return a+b}) // 求和
      } else {
          return addCurry;
      }
  }
addCurry(1)(2)(3)()

至此,核心逻辑已经实现。

以上代码清晰地解释了为什么可以说:柯里化等于闭包加上递归。

柯里化不仅仅是一种语法技巧,更是一种编程思想。上述 addCurry 是最基础的实践。在函数式编程中,柯里化应用广泛,例如 compose(fn1)(fn2)(fn3)…(fnN)(args) 等组合模式。

如果未来有人询问柯里化的原理,可以从这个角度进行解答。

以上就是闭包结合递归等于柯里化原理解析的详细内容,更多关于闭包结合递归等于柯里化的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章