如何理解 arguments
arguments 到底是什么?它是数组吗?为什么普通函数有它而箭头函数没有?它和 ...args 有什么本质区别?
一、核心结论
arguments 是一个类数组对象,由 ECMAScript 引擎在普通函数调用时自动创建,它反映了函数被调用时的实际参数列表。
- 它不是数组,但有
length和索引 - 它是“执行上下文”的一部分,在函数创建时动态生成
- 箭头函数没有
arguments,因为它不参与“传统函数上下文创建协议” ...args是现代语法,是真正的数组,且更清晰、更安全
二、规范定义:arguments 是什么?
来自 ECMA-262 §10.2.4:
当一个普通函数(非箭头函数)被调用时,引擎会创建一个 Function Environment Record,其中包含:
[[ThisValue]]→this的值[[NewTarget]]→new调用的目标[[HomeObject]]→super查找起点[[FormalParameters]]→ 形参列表[[ParameterMap]]→ (仅严格模式下不存在)形参与arguments的绑定映射
关键点:
arguments 是 环境记录(Environment Record) 的一部分
- 它不是一个局部变量,而是执行上下文的内置结构
- 在函数体中访问
arguments,其实是查询当前上下文的“参数环境”
三、arguments 的结构与行为
function foo(a, b) {
console.log(arguments);
console.log(arguments[0], arguments[1]); // a, b
console.log(arguments.length); // 实际传入参数数量
}
foo(1, 2, 3); // arguments = { '0': 1, '1': 2, '2': 3, length: 3 }1. 类数组(Array-like)结构
| 属性 | 说明 |
|---|---|
[index] | 可通过数字索引访问参数 |
length | 实际传入的参数个数 |
| 没有数组方法 | 如 push、map、forEach |
arguments.map(x => x * 2); // ❌ 报错,arguments 不是 Array 实例
Array.from(arguments).map(x => x * 2); // ✅ 正确用法2. 动态性:反映实际调用参数
function bar(x) {
return arguments.length; // 不依赖形参,看实参
}
bar(); // 0
bar(1); // 1
bar(1,2,3); // 3四、最神奇的部分:arguments 与形参的绑定(仅非严格模式)
在 非严格模式 下,arguments 和命名参数是双向绑定的!
function baz(a, b) {
console.log(a, arguments[0]); // 1, 1
a = 10;
console.log(a, arguments[0]); // 10, 10 ← 同步变化!
arguments[1] = 20;
console.log(b); // 20 ← b 也被改了!
}
baz(1, 2);为什么?
因为非严格模式下,引擎创建了一个 [[ParameterMap]] ——
这是一个内部表,将形参 a、b 映射到 arguments[0]、arguments[1]。
这种绑定在 严格模式下被禁用:
function strict(a, b) {
'use strict';
a = 10;
console.log(arguments[0]); // 1 ← 不再同步!
}五、为什么箭头函数没有 arguments?
来自规范:§15.3.2 Runtime Semantics: Evaluation
箭头函数没有自己的 arguments 绑定。
如果你在箭头函数中访问 arguments,它会像普通变量一样,沿词法环境链向上查找。
function outer() {
const arrow = () => {
console.log(arguments); // 访问的是 outer 的 arguments
};
arrow(1, 2, 3);
}
outer('a', 'b'); // 输出: ['a', 'b']所以:箭头函数没有 arguments,是因为它不创建自己的 Function Environment Record,自然也没有 arguments 这个内部绑定。
六、arguments vs ...args:一场新旧对话
| 特性 | arguments | ...args(剩余参数) |
|---|---|---|
| 类型 | 类数组对象 | 真正的数组 |
| 是否可变 | 是(但不推荐) | 是 |
| 是否包含所有实参 | 是 | 是 |
| 是否受形参影响 | 是(索引对应) | 否(只收集未命名的) |
是否有 callee | 是(arguments.callee) | 否 |
| 严格模式兼容 | 是(但无绑定) | 是 |
| 可读性 | 差(隐式存在) | 好(显式声明) |
| 性能 | 较差(阻止 V8 优化) | 好 |
推荐写法(现代 JS):
function modern(...args) {
return args.map(x => x * 2); // ✅ 直接使用数组方法
}七、arguments.callee:被废弃的“递归神器”
在早期 JS 中,匿名函数无法自我调用,于是有了:
// 匿名递归函数
(function(n) {
if (n <= 1) return 1;
return n * arguments.callee(n - 1); // 调用自己
})(5);但:
- 严格模式下禁止使用
- 破坏优化(V8 无法内联)
- 可读性差
现代替代方案:
// 方案1:命名函数表达式
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
// 方案2:外部变量
const fib = n => (n <= 1 ? n : fib(n-1) + fib(n-2));八、图解:arguments 在执行上下文中的位置
执行上下文(foo 调用)
│
├── [[ThisBinding]]: window/global
├── [[LexicalEnvironment]]:
│ └── 环境记录:
│ ├── a: 1
│ ├── b: 2
│ └── arguments: { '0': 1, '1': 2, '2': 3, length: 3 }
│ ← arguments 与 a、b 双向绑定(非严格模式)
│
└── [[VariableEnvironment]]: 同上(通常一致)九、一句话总结
arguments 是普通函数在调用时,由引擎自动创建的一个“类数组对象”,它是 Function Environment Record 的一部分,反映了实际传入的参数。
- 它不是数组,但可通过索引访问
- 非严格模式下与形参双向绑定
- 箭头函数没有它,因为它不创建完整的执行上下文
- 现代开发应优先使用
...args,更清晰、更安全、更易优化
总结:
理解 arguments,就是理解 JS 的“历史与进化”
arguments 像是一个“老派工匠”:
- 功能强大,但有点粗糙
- 曾经是唯一的解决方案
- 如今被更优雅的工具取代
但理解它,你就能明白:
- 为什么 JS 一开始设计成这样
- 为什么严格模式要禁用某些行为
- 为什么现代语法(如
...args)是更好的选择