作为前端开发者,我们每天都在和函数打交道。但你有没有想过,函数不仅仅是执行代码的容器,它本身就是一个对象?今天我们来聊聊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)); // 这样会报错
}
arguments有length属性,可以用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函数的精妙之处在于:
- 闭包的运用:
fn被保存在闭包中,任何地方都可以访问到 - 递归的思想:通过返回函数的函数,实现参数的逐步收集
- 参数累积:每次调用都会将新参数与之前的参数合并
- 条件判断:当参数数量达到原函数要求时(
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)的执行过程:
-
第一次调用:
addCurry(1)args = [1],fn.length = 31 < 3,返回新函数
-
第二次调用:返回的函数
(2)args = [1, 2],fn.length = 32 < 3,继续返回新函数
-
第三次调用:返回的函数
(3)args = [1, 2, 3],fn.length = 33 == 3,执行fn(1, 2, 3),返回6
闭包的核心作用
闭包的定义很简单:可以访问自由变量的函数就是闭包。在我们的curry函数中,judge函数可以访问外部的fn变量,这就是闭包的典型应用。
函数确实是"一等对象",也就是说函数也是对象。这意味着函数可以:
- 作为参数传递
- 作为返回值返回
- 赋值给变量
- 存储在数据结构中
总结
arguments和curry虽然是两个不同的概念,但它们都体现了JavaScript函数的强大和灵活性:
- Arguments让我们能够灵活处理不定数量的参数,虽然现在更推荐使用剩余参数
- Curry让我们能够优雅地处理参数的逐步收集,体现了函数式编程的思想
柯里化不仅仅是一个编程技巧,更是一种编程思维的转变。它让我们从"一次性传入所有参数"转向"逐步收集参数",从"写死的函数"转向"可配置的函数工厂"。
掌握这些概念不仅能让我们写出更优雅的代码,更重要的是能帮我们理解JavaScript函数的本质:函数是一等对象,它们可以被传递、返回、组合,这正是JavaScript灵活性的体现。