深入理解柯里化与闭包

前言

柯里化

柯里化(Currying),又称为部分求值(Partial Evaluation),是函数式编程中的一种技术。它的核心思想是将原本接受多个参数的函数转换为一系列单参数函数的嵌套调用。

具体来说,假设有一个函数 f(a, b, c),经过柯里化之后,可以转换为 f(a)(b)(c)。这样的转换带来了几个好处:

  1. 提高函数的复用性:可以通过提前固定一部分参数来创建新的函数,这样就减少了代码重复,增强了代码的灵活性和模块化。
  2. 函数组合:柯里化使得函数更容易进行组合,可以通过简单地将一个函数的输出作为另一个函数的输入来构建复杂的操作,提高代码的可读性和可维护性。
  3. 延迟计算:因为参数是逐个提供的,可以在需要时才计算最终结果,这对于控制计算流程和优化性能非常有用,尤其是在处理昂贵的运算或者条件执行逻辑时。

正文

两数相加,接受两个参数

两数相加,用之前的思维来就是函数接受两个参数,最后返回两数相加。

如下所示,这里面还增加了两个判断语句。使用arguments对象检查传入的参数数量是否少于2,如果参数不足,打印提示信息并直接返回,避免后续代码执行;检查传入参数a和b的数据类型是否都是数字。

js 复制代码
function add(a, b){
    if (arguments.length < 2){
        console.log('参数数量不够哦');
        return;
    }
    // 数据类型 参数数量的问题
    if (typeof a != 'number' || typeof b != 'number'){
        console.log('类型错误');
        return;
    }
    return a + b;
}

console.log(add(2, 3));

结合闭包实现两数相加,一次只接收一个参数

add函数返回了一个内部函数,这个内部函数记住了它被创建时的外部环境中的变量a,即使在add函数执行完毕后,这个内部函数依然能够访问到a的值。

当我们两次调用add函数时(第一次传入2,第二次通过返回的函数传入4),实际上利用了闭包的特性来累加这两个数值并最终输出结果6。

js 复制代码
// add2 = add(2) 返回的仍然是一个函数
// add2(4) = 6

function add(a){
    // 第一个参数a
    
    return function(b){
    // 这里的a是外部add函数的参数,对于这个内部函数来说,a就是一个自由变量
        return a + b;
    }  
}

console.log(add(2)(4));

参数不定时,柯里化结合闭包

思路:

  • 首先定义一个函数curry,接收两个参数fn和...args,fn是需要被柯里化的函数,...args是当前收集的参数。
  • 原函数add是需要被柯里化的函数,在这个函数中接收4个参数
  • 在curry函数中判断当前收集的参数数量args.length是否等于原函数fn需要的参数数量fn.length
  • 如果收集的参数足够,也就是args.length >= fn.length,那么就使用rest运算符展开args,直接调用原函数add并返回结果。
  • 如果收集的参数还不够,也就是args.length < fn.length,那么就返回一个新的函数curry(fn, ...args, ..._args),这个函数继续收集剩下的参数_args
js 复制代码
// 只有一句代码,是返回值本身
const curry = (fn, ...args) => 
    args.length >= fn.length ? fn(...args) : (..._args) => curry(fn, ...args, ..._args)

// 原函数add
// 柯里化 慢慢收集
const add = (x, y, z, m) => {
    return x + y + z + m;
}

console.log(curry(add, 2)(2)(3, 4)); // 输出结果是11,相当于调用了add(2, 2, 3, 4)

使用curry函数对add函数进行柯里化处理,先传入第一个参数2,然后逐步传递剩余参数,每次调用返回一个新的函数等待下一个参数

这段代码首先定义了一个curry函数,它负责逐步收集参数并判断是否已收集齐原函数所需的全部参数。如果收集齐了,就执行原函数;如果没有,就继续返回一个函数等待更多的参数。之后,通过curry函数将一个多参数的add函数转换成可以逐个传参的形式,并通过一系列函数调用计算出了最终结果11。

小知识点

在JavaScript中,rest运算符和扩展运算符虽然都用三个点(...)表示,但它们在用途上有明显的区别:

  1. Rest运算符(...):

    • 主要用于收集函数调用时传入的多余参数,将这些参数放入一个数组中。
    • 在数组解构赋值中,也可以用来捕获数组中剩余的元素,将它们组合成一个新的数组。

    示例:

js 复制代码
function sum(...numbers) {
    let total = 0;
    numbers.forEach(num => total += num);
    return total;
}
sum(1, 2, 3, 4); // 返回10

// 解构赋值中的Rest
const [a, b, ...others] = [1, 2, 3, 4, 5];
console.log(others); // 输出 [3, 4, 5]
  1. 扩展运算符(...):

    • 扩展运算符可以简化数组操作,比如合并数组、复制数组或对象,以及将数组元素作为函数参数传递。

    示例:

    javascript 复制代码
    // 合并数组
    const arr1 = [1, 2, 3];
    const arr2 = [4, 5, 6];
    const combined = [...arr1, ...arr2]; // 结果是 [1, 2, 3, 4, 5, 6]
    
    // 作为函数参数传递
    function concat(a, b, c) {
        console.log(a, b, c);
    }
    const args = [1, 2, 3];
    concat(...args); // 输出 1 2 3

总结来说,rest运算符主要用于收集多个项为一个数组,而扩展运算符则是将数组拆分为多个单项或合并数组和对象。两者虽符号相同,但在具体应用中功能恰好互补。

结语

本文深入探讨了函数式编程中的核心概念------柯里化(Currying)及其在JavaScript中的应用,希望可以给你带来帮助。

相关推荐
辻戋12 分钟前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保14 分钟前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun1 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp1 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.2 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl4 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫6 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友6 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理8 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻8 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js