深入理解柯里化与闭包

前言

柯里化

柯里化(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中的应用,希望可以给你带来帮助。

相关推荐
卡夫卡的小熊猫1 小时前
vue前端菜单权限控制
前端
祈澈菇凉2 小时前
什么是 Vue 的自定义事件?如何触发和监听?
前端·javascript·vue.js
2301_766536053 小时前
调试无痛入手
开发语言·前端
@大迁世界4 小时前
构建 Next.js 应用时的安全保障与风险防范措施
开发语言·前端·javascript·安全·ecmascript
IT、木易5 小时前
ES6 新特性,优势和用法?
前端·ecmascript·es6
is今夕6 小时前
postcss.config.js 动态配置基准值
javascript·vue.js·postcss
青茶绿梅*26 小时前
500字理透react的hook闭包问题
javascript·react.js·ecmascript
计算机软件程序设计6 小时前
vue和微信小程序处理markdown格式数据
前端·vue.js·微信小程序
指尖时光.6 小时前
【前端进阶】01 重识HTML,掌握页面基本结构和加载过程
前端·html