Skip to content

如何理解 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 的结构与行为

js
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实际传入的参数个数
没有数组方法pushmapforEach
js
arguments.map(x => x * 2); // ❌ 报错,arguments 不是 Array 实例
Array.from(arguments).map(x => x * 2); // ✅ 正确用法

2. 动态性:反映实际调用参数

js
function bar(x) {
  return arguments.length; // 不依赖形参,看实参
}
bar();        // 0
bar(1);       // 1
bar(1,2,3);   // 3

四、最神奇的部分:arguments 与形参的绑定(仅非严格模式)

非严格模式 下,arguments 和命名参数是双向绑定的!

js
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]] ——
这是一个内部表,将形参 ab 映射到 arguments[0]arguments[1]

这种绑定在 严格模式下被禁用

js
function strict(a, b) {
  'use strict';
  a = 10;
  console.log(arguments[0]); // 1 ← 不再同步!
}

五、为什么箭头函数没有 arguments

来自规范:§15.3.2 Runtime Semantics: Evaluation

箭头函数没有自己的 arguments 绑定。

如果你在箭头函数中访问 arguments,它会像普通变量一样,沿词法环境链向上查找。

js
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):

js
function modern(...args) {
  return args.map(x => x * 2); // ✅ 直接使用数组方法
}

七、arguments.callee:被废弃的“递归神器”

在早期 JS 中,匿名函数无法自我调用,于是有了:

js
// 匿名递归函数
(function(n) {
  if (n <= 1) return 1;
  return n * arguments.callee(n - 1); // 调用自己
})(5);

但:

  • 严格模式下禁止使用
  • 破坏优化(V8 无法内联)
  • 可读性差

现代替代方案:

js
// 方案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)是更好的选择