先记住这个答案
arguments 是类数组对象,有 length 和数字索引,但没有 map、filter 等数组方法。转真数组常用三招:Array.prototype.slice.call(arguments) 靠借用数组方法做浅拷贝,兼容性最好;Array.from(arguments) 语义最清晰,专为类数组和可迭代对象设计;[...arguments] 利用 arguments 自带的 Symbol.iterator 展开,最简洁。三者都是浅拷贝,性能差距在常规规模下可忽略,按可读性选即可。更简单做法是直接用剩余参数 ...args 声明,天生就是数组。
- slice.call 兼容老环境,Array.from 语义最清晰
- 展开运算符依赖 Symbol.iterator,写法最简
- 箭头函数没有 arguments,应用剩余参数
三种转换方式的机制差异
arguments 是类数组对象:有 length、有从 0 开始的索引,也实现了 Symbol.iterator,但原型链上没有数组方法。Array.prototype.slice.call(arguments) 的原理是把 slice 的 this 指向 arguments,slice 内部只依赖 length 和索引读取元素,因此对任何类数组都成立,这也是它能在 ES6 之前通行的原因。
Array.from 是 ES6 专门为类数组和可迭代对象设计的工厂方法,语义最直白,还能传第二个映射参数一次完成转换加处理。[...arguments] 走的是迭代协议,逐项调用迭代器取值。三者产生的都是浅拷贝新数组,元素引用与原值共享;对稀疏的 arguments(实参少于形参时不会出现空槽,因为只记录实参)差异不大,性能在常见参数规模下没有可感知差距。
function demo(a, b) {
const bySlice = Array.prototype.slice.call(arguments);
const byFrom = Array.from(arguments);
const bySpread = [...arguments];
console.log(Array.isArray(bySlice), Array.isArray(byFrom), Array.isArray(bySpread));
console.log(JSON.stringify(bySlice));
console.log(JSON.stringify(byFrom.map(x => x * 2)));
console.log(JSON.stringify(bySpread));
}
demo(1, 2, 3);查看输出与解释
true true true
[1,2,3]
[2,4,6]
[1,2,3]在任意现代浏览器控制台或 Node 环境可直接运行,三种方式均得到真数组且内容一致。
可变参数日志函数的选型决策
维护一个 ES5 时代的老项目,其中 log(level) 后接任意多个消息片段,构建工具不能引入 ES6 语法,因为目标环境包含旧浏览器。输入是 log('info', 'user', 42, 'login') 这类不定参数,约束是必须跳过第一个参数再拼接。此时只能选 Array.prototype.slice.call(arguments, 1),一次调用完成转数组加截断,结果是得到可 join 的真数组。
如果同一功能写在新代码里,决策应改为 function log(level, ...msgs):msgs 天生是数组,无需转换,也不会出现「忘记转换直接调用 msgs.join 报 TypeError」的问题。选择依据不是性能,而是语法可用性与出错概率:转换是补救手段,剩余参数才是源头解法。
容易失效的条件与替代方案
最常见的失败场景是在箭头函数里访问 arguments:箭头函数不创建自己的 arguments,读到的是外层普通函数的 arguments,外层也没有时直接抛 ReferenceError。另一个边界是含默认参数、剩余参数或解构参数的非严格函数,以及所有严格模式函数:arguments 不再与形参同步,修改转换出的数组或 arguments 互不影响,依赖同步逻辑会出错。
对应处理:箭头函数内改用剩余参数收集实参,或显式引用外层具名函数的 arguments;需要「参数别名」效果时应直接操作形参变量而非 arguments。代价是放弃了 arguments 的神秘同步行为,换来的是严格模式下可预测、引擎更容易优化的代码。
容易答错的地方
- arguments 是数组的一种
typeof arguments是'object',它没有数组原型方法,直接调arguments.map会抛 TypeError。它只是类数组且可迭代,必须先转换或改用剩余参数。- slice.call 性能远超其他方法
- 现代引擎对三种方式都有优化,常规参数数量下耗时差异不可测。选型应按兼容性和可读性:老环境用
slice.call,新环境用Array.from或展开运算符。
面试官还会怎么问?
为什么新代码推荐剩余参数而不是转换 arguments?
剩余参数 ...args 声明后就是真数组,无需转换,且不与形参产生同步关系,行为可预测。箭头函数也能用,而 arguments 在箭头函数中不可用。
转换后的数组和原 arguments 会互相影响吗?
不会。三种方式都是浅拷贝:数组结构独立,增删元素互不影响;但元素若是对象,引用共享,改对象属性两边可见。
arguments 有 Symbol.iterator 意味着什么?
意味着它可迭代,能被 for...of、展开运算符和 Array.from 直接消费。这也是 [...arguments] 成立而 [...{length:2,'0':'a'}] 这类纯类数组失败的原因。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。