深入理解JavaScript核心:arguments与curry的深度解析

124 阅读5分钟

作为前端开发者,我们每天都在和函数打交道。但你有没有想过,函数不仅仅是执行代码的容器,它本身就是一个对象?今天我们来聊聊JavaScript中两个非常有趣的概念:arguments对象和柯里化(Curry),看看它们是如何让我们的代码变得更加灵活和优雅的。

Arguments:函数的参数总管

什么是Arguments?

在JavaScript中,每当我们调用一个函数时,都会自动创建一个特殊的对象叫做arguments。它就像是"函数运行时的参数总管",负责管理所有传入的参数。

function add(a, b, c) {
    // arguments 函数运行是,参数总管
    // 下标访问第几个参数 数组
    console.log(arguments, arguments.length, Object.prototype.toString.call(arguments));
}

Arguments的特殊性质

arguments对象有个很有趣的特点:它看起来像数组,有length属性,可以用下标访问,但它并不是真正的数组。我们称它为"类数组对象"。

function add(a, b, c) {
    //类数组,有length属性,for,但是没有数组太多的方法
    // console.log(arguments.map(item => item +1)); // 这样会报错
}

argumentslength属性,可以用for循环遍历,但是没有数组的那么多方法,比如map方法就不能直接使用。

将Arguments转换为真正的数组

既然arguments不是真正的数组,那我们怎么使用数组的那些强大方法呢?答案是转换!

function add(a, b, c) {
    //如何将类数组转成真正的数组?
    const args = Array.from(arguments);
    console.log(Object.prototype.toString.call(args));
    console.log(args);
    let result = 0;
    for (let i = 0; i < args.length; i++) {
        console.log(args[i]);
        result += args[i];
    }
    return result;
}
console.log(add.length);
console.log(add(1, 2, 3));

使用Array.from(arguments)可以将类数组转换为真正的数组,这样就可以使用所有数组方法了。

现代替代方案

现代JavaScript中,我们更推荐使用剩余参数(rest parameters)来替代arguments

function add(...args){
    console.log(args);
    return(...newArgs) => {
        const arr = [...args,...newArgs];
        console.log(arr)
    }
}
add(1,2,3)(4,5,6)

这个例子展示了剩余参数的使用,...args直接就是一个真正的数组,不需要转换。

Curry:参数收集的艺术

什么是柯里化?

柯里化是将多参数函数转化为一系列单参数函数的技术。在现实中,函数的参数往往是逐步收集的,就像收集七颗龙珠一样,当我们集齐所有参数时,就可以"召唤神龙"执行最终的功能了。

柯里化的核心思想

柯里化的本质是闭包 + 递归的完美结合:

  • 闭包:保存原函数和已收集的参数
  • 递归:不断返回新函数,直到参数收集完毕
  • 延迟执行:只有在参数足够时才执行原函数

简单来说,就是"返回函数的函数",通过递归的方式,每个参数的收集都是相似的小任务。

手写Curry函数详解

让我们来看看实际的curry函数实现:

function add(a, b, c) {
    return a + b + c;
}

function curry(fn) {
    // fn? 参数 最终要执行的功能,闭包中的自由变量
    // curry 函数 包装fn,慢慢收集参数
    // 递归
    //返回一个函数
    //...args 所有的参数 自由变量
    let judge = (...args) => {
        // 任何地方都可以访问到定义时候的fn
        if(args.length == fn.length){
            //退出条件
            return fn(...args);
        }
        return (...newArgs) => judge (...args,...newArgs);
    }
    return judge; // 关键:返回 judge 函数
}

这个curry函数的精妙之处在于:

  1. 闭包的运用fn被保存在闭包中,任何地方都可以访问到
  2. 递归的思想:通过返回函数的函数,实现参数的逐步收集
  3. 参数累积:每次调用都会将新参数与之前的参数合并
  4. 条件判断:当参数数量达到原函数要求时(args.length == fn.length),执行最终逻辑

柯里化的使用

// 柯里化 将一个多参数的函数转换为一系列的单参数函数
// 手写curry函数
let addCurry = curry(add);
// 逐步的去获取函数需要的参数,当到达fn 需要的参数数量时,执行fn
console.log(addCurry(1)(2)(3));

我们逐步获取函数需要的参数,当到达fn需要的参数数量时,执行fn。

函数长度的重要性

在JavaScript中,fn.length返回函数定义时的参数个数,这是柯里化判断何时执行原函数的关键依据。真正要执行的函数fn,它的参数数量是明确的。

柯里化的执行流程

让我们跟踪一下addCurry(1)(2)(3)的执行过程:

  1. 第一次调用addCurry(1)

    • args = [1], fn.length = 3
    • 1 < 3,返回新函数
  2. 第二次调用:返回的函数(2)

    • args = [1, 2], fn.length = 3
    • 2 < 3,继续返回新函数
  3. 第三次调用:返回的函数(3)

    • args = [1, 2, 3], fn.length = 3
    • 3 == 3,执行 fn(1, 2, 3),返回 6

闭包的核心作用

闭包的定义很简单:可以访问自由变量的函数就是闭包。在我们的curry函数中,judge函数可以访问外部的fn变量,这就是闭包的典型应用。

函数确实是"一等对象",也就是说函数也是对象。这意味着函数可以:

  • 作为参数传递
  • 作为返回值返回
  • 赋值给变量
  • 存储在数据结构中

总结

argumentscurry虽然是两个不同的概念,但它们都体现了JavaScript函数的强大和灵活性:

  • Arguments让我们能够灵活处理不定数量的参数,虽然现在更推荐使用剩余参数
  • Curry让我们能够优雅地处理参数的逐步收集,体现了函数式编程的思想

柯里化不仅仅是一个编程技巧,更是一种编程思维的转变。它让我们从"一次性传入所有参数"转向"逐步收集参数",从"写死的函数"转向"可配置的函数工厂"。

掌握这些概念不仅能让我们写出更优雅的代码,更重要的是能帮我们理解JavaScript函数的本质:函数是一等对象,它们可以被传递、返回、组合,这正是JavaScript灵活性的体现