JavaScript 的 arguments 详解
Category(分类): JavaScript Status: 已整理(2026)
原文只有标题和来源链接。这里补齐
arguments的规范行为、常见误区和现代替代写法,重点保留它在面试题、可变参数函数和参数转发中的使用价值。
一、arguments 是什么
arguments 是普通函数内部可用的一个类数组对象,表示本次调用实际传入的所有参数。它有从 0 开始的数字索引和 length 属性,但它不是 Array 实例:
function inspect(first, second) {
console.log(arguments[0]) // first
console.log(arguments[1]) // second
console.log(arguments[2]) // 即使没有形参,也能访问额外参数
console.log(arguments.length) // 实际传入的参数数量
console.log(Array.isArray(arguments)) // false
}
inspect('a', 'b', 'c')
它表示“调用者传入了什么”,而函数的 length 属性表示“函数声明中第一个带默认值参数之前有多少个形参”:
function example(a, b, c = 3, ...rest) {}
console.log(example.length) // 2:只计算默认参数之前的形参
function count(a, b) {
return arguments.length
}
console.log(count(1)) // 1
console.log(count(1, 2, 3)) // 3
arguments.length 和 function.length 解决的是不同问题,不能混用。
二、哪些函数拥有 arguments
arguments 是函数调用上下文中的局部绑定,普通函数、方法、构造函数和 class 构造器中都可以使用;箭头函数没有自己的 arguments:
function normal() {
return arguments.length
}
const object = {
method() {
return arguments[0]
}
}
class Example {
constructor() {
this.count = arguments.length
}
}
console.log(normal(1, 2)) // 2
console.log(object.method('value')) // value
console.log(new Example(1, 2).count) // 2
箭头函数如果直接放在模块或脚本顶层,不能凭空获得一个函数参数列表;如果它嵌套在普通函数中,则会捕获外层普通函数的 arguments:
function outer(first, second) {
const readArguments = () => arguments[1]
return readArguments()
}
console.log(outer('a', 'b')) // b
不要把“箭头函数没有自己的 arguments”理解为“箭头函数永远不能访问名为 arguments 的变量”;它可以通过词法作用域读取外层绑定。
三、它是类数组对象,不是数组
arguments 有数字索引和 length,但没有 map、filter、forEach 等数组实例方法:
function listArguments() {
console.log(typeof arguments) // object
console.log(arguments.map) // undefined
const array = Array.from(arguments)
return array.map(value => String(value).toUpperCase())
}
console.log(listArguments('a', 'b')) // ['A', 'B']
现代 JavaScript 可以直接使用以下方式转换:
function toArray() {
const byFrom = Array.from(arguments)
const bySpread = [...arguments]
const bySlice = Array.prototype.slice.call(arguments)
return { byFrom, bySpread, bySlice }
}
console.log(toArray(1, 2, 3))
arguments 在现代 ECMAScript 中是可迭代的,因此展开语法可用;非常老的运行环境可能只支持 slice.call。新代码更推荐 rest 参数,因为它直接创建真正的数组。
四、arguments 与 rest 参数
历史代码经常这样写:
function sum() {
return Array.from(arguments).reduce((total, value) => total + value, 0)
}
console.log(sum(1, 2, 3)) // 6
现代写法是 rest 参数:
function sumModern(...numbers) {
return numbers.reduce((total, value) => total + value, 0)
}
console.log(sumModern(1, 2, 3)) // 6
二者的主要区别:
| 特性 | arguments | rest 参数 |
|---|---|---|
| 类型 | 类数组对象 | 真正的数组 |
| 是否有自己的绑定 | 普通函数有,箭头函数没有 | 由参数声明直接提供 |
| 只收集部分参数 | 需要索引或 slice | function f(first, ...rest) |
| 与形参联动 | 非严格简单参数可能联动 | 是独立数组值 |
| 可读性 | 适合维护旧代码 | 新代码首选 |
| 可用于箭头函数 | 不能创建自己的 arguments | 可以 |
function logFirstAndRest(first, ...rest) {
console.log(first) // 第一个参数
console.log(rest) // 其余参数组成的新数组
}
logFirstAndRest('first', 'second', 'third')
如果只需要转发全部参数,rest 与 spread 的组合通常最清楚:
function callWithLogging(fn, ...args) {
console.log('calling with', args)
return fn(...args)
}
console.log(callWithLogging(Math.max, 3, 1, 5)) // 5
五、映射的 arguments 和非映射的 arguments
这是 arguments 最容易被忽略的历史细节。
5.1 非严格模式的简单参数
在非严格模式、且函数使用简单参数列表时,具名形参与 arguments 的对应索引可能共享同一参数绑定:
function sloppy(a) {
arguments[0] = 99
console.log(a) // 99
a = 100
console.log(arguments[0]) // 100
}
sloppy(1)
这里的“简单参数列表”不包含默认参数、rest 参数或解构参数,例如 function f(a, b) 是简单的,function f(a = 1) 不是。
5.2 严格模式中不再联动
严格模式下,形参和 arguments 索引互相独立:
function strictExample(a) {
'use strict'
arguments[0] = 99
console.log(a) // 1
a = 100
console.log(arguments[0]) // 99
}
strictExample(1)
ES modules 和 class 方法默认使用严格模式。
5.3 非简单参数列表也不联动
即使没有显式写 'use strict',默认参数、rest 或解构参数也会让 arguments 与具名参数保持独立:
function withDefault(value = 10) {
arguments[0] = 99
console.log(value) // 1
value = 100
console.log(arguments[0]) // 99
}
withDefault(1)
另外,带默认参数、rest 或解构参数的函数体不能再使用函数级的 'use strict' 指令:
// SyntaxError:非简单参数列表的函数体不能写函数级 use strict 指令
// function invalid(value = 1) {
// 'use strict'
// }
如果需要严格模式,可以让整个脚本或模块处于严格模式,而不是在该函数体中单独写指令。
六、arguments 的常用场景
6.1 实现可变参数函数
function longestString() {
if (arguments.length === 0) {
throw new TypeError('至少传入一个字符串')
}
let longest = ''
for (const value of arguments) {
if (value.length > longest.length) longest = value
}
return longest
}
console.log(longestString('a', 'hello', 'js')) // hello
新代码可以使用 rest:
function longestStringModern(...values) {
if (values.length === 0) throw new TypeError('至少传入一个字符串')
return values.reduce((longest, value) =>
value.length > longest.length ? value : longest,
''
)
}
6.2 保留第一个形参,再处理剩余参数
function concat(separator) {
const values = Array.prototype.slice.call(arguments, 1)
return values.join(separator)
}
console.log(concat(', ', 'red', 'green', 'blue')) // red, green, blue
使用 rest 参数更直观:
function concatModern(separator, ...values) {
return values.join(separator)
}
6.3 通过 apply 转发类数组参数
Function.prototype.apply 接受数组或类数组对象,所以旧代码可以直接把 arguments 传给它:
function midpoint() {
return (
(Math.min.apply(null, arguments) + Math.max.apply(null, arguments)) / 2
)
}
console.log(midpoint(3, 1, 4, 1, 5)) // 3
现代写法:
function midpointModern(...values) {
return (Math.min(...values) + Math.max(...values)) / 2
}
如果要保留 this,使用 Reflect.apply 或 fn.apply(context, args):
function invoke(fn, context, argsLike) {
return Reflect.apply(fn, context, argsLike)
}
function add(first, second) {
return this.base + first + second
}
console.log(invoke(add, { base: 10 }, [1, 2])) // 13
七、不要依赖 arguments.callee 和 arguments.caller
历史代码有时通过 arguments.callee 获得当前函数:
function factorial(n) {
if (n <= 1) return 1
return n * arguments.callee(n - 1)
}
这种写法会阻碍优化、破坏可读性,并且在严格模式中访问 arguments.callee 会抛出 TypeError。直接给函数命名即可:
const factorial = function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
console.log(factorial(5)) // 120
arguments.caller 也不是可靠的标准调用栈 API。需要调试调用栈时使用开发者工具、Error().stack(注意它是宿主相关信息)或显式传递上下文。
八、常见误区
8.1 arguments 不会自动深拷贝参数
function mutate(first) {
first.value = 2
}
const object = { value: 1 }
mutate(object)
console.log(object.value) // 2:传入的是对象引用
arguments[0] 保存的是传入值;对象值仍然指向原对象。是否复制对象与 arguments 无关。
8.2 arguments 只属于当前函数
function outer() {
console.log(arguments.length) // 2
function inner() {
console.log(arguments.length) // 1:这是 inner 的 arguments
}
inner('inner value')
}
outer('a', 'b')
如果内部使用箭头函数,则会词法捕获外层的 arguments;如果使用普通函数,则拥有自己的 arguments。
8.3 形参没有赋值时也可能有索引
function optional(first, second) {
console.log(arguments.length)
console.log(arguments[1])
}
optional('only first') // 1,undefined
arguments[1] 是 undefined 不代表调用者实际传入了第二个参数。需要判断是否真的传入,可以结合 arguments.length 或 Object.hasOwn(arguments, '1'):
function wasProvided(value) {
return arguments.length > 0
}
console.log(wasProvided(undefined)) // true
console.log(wasProvided()) // false
九、总结
arguments是普通函数内部的类数组对象,记录实际传参;arguments.length是实际参数数量,Function.length是声明参数数量的另一种统计;- 箭头函数没有自己的
arguments,但可以捕获外层普通函数的arguments; - 非严格模式的简单参数可能与
arguments索引联动,严格模式和非简单参数不会; arguments没有数组方法,使用Array.from、展开语法或slice.call转换;- 新代码优先使用 rest 参数;维护旧代码时要注意
arguments.callee在严格模式中不可用; - 参数对象不会改变对象引用语义,也不会自动复制或验证参数。