先记住这个答案
类数组对象指的是具有 length 属性和从零开始的索引(如 arguments[0])的对象,但不继承数组方法。arguments 对象就是典型例子,它可被索引、可迭代,但没有 forEach、map 等方法。与真实数组的区别在于原型链不同。要转换为数组,可使用 Array.from(arguments) 或 [...arguments](因为 arguments 可迭代)。
- 类数组对象有 length 和索引但无数组方法。
- arguments 是这类对象的典型,但不能直接调用 push。
- 用 Array.from(arguments) 可得到真正的数组。
arguments 的内存与原型机制
arguments 是函数每次调用时自动生成的类数组对象。它存储了调用时传入的所有实参,具有数字索引和长度,但不是数组,因为它不继承 Array.prototype。例如,arguments.forEach 是 undefined,而数组可以调用该方法。
arguments 实际上是特殊的可迭代对象,具有 Symbol.iterator 属性,因此可以用 for...of 遍历或用展开运算符展开。它与普通数组的另一差异是,在非严格模式下简单函数的 arguments 和命名参数会同步,严格模式下则不同步。
实现一个接收任意参数的求和函数
情景:一个函数 sum 需要接收任意数量的数字并求和。作为约束,不能使用箭头函数的 rest 语法(要求用 arguments 实现)。调用 sum(1,2,3) 期望返回 6。处理方法是,在函数内部,先把 arguments 转换为真实数组,然后用 reduce 求和。
如果不转换,arguments 没有 reduce 方法,必须用循环遍历。转换后代码更简洁:Array.from(arguments).reduce((a,b)=>a+b,0)。此时 arguments 的 length 和索引依然存在,但 reduce 可用,结果正确。注意,只有当 arguments 可迭代时才能用展开运算符。
何时转换可能失败或产生意外
在箭头函数中,它自身没有独立的 arguments 对象;若箭头函数定义在普通函数内部,它访问的 arguments 会继承外层函数的 arguments,而非本次调用的实参。因此在箭头函数内想要收集本次调用的实参,不能依赖 arguments,应使用 rest 参数(...args)。检查是否是非箭头函数,或改用 rest 是更安全的做法。
转换本身也有代价:Array.from(arguments) 会创建新数组,若实参数量极大(数十万),会消耗额外内存。不过实际场景中实参数量通常有限,但若对性能敏感,直接用 for 循环遍历 arguments 可能更快,避免中间数组。
容易答错的地方
- 认为 arguments 是数组
- 很多人误以为 arguments 是一个数组,因为它看起来像。但实际上它没有数组方法,比如 push 不存在。typeof arguments 返回 'object',且 Array.isArray(arguments) 为 false。
- 忽视转换方法的迭代器要求
- 展开运算符 [...arguments] 依赖 arguments 的可迭代性,这是可用的,因为 arguments 实现了 Symbol.iterator。但并非所有类数组对象都可迭代(如 {0:'a', length:1}),此时必须用 Array.from 或 Array.prototype.slice.call。
面试官还会怎么问?
arguments.callee 是干什么的?在严格模式下能用吗?
arguments.callee 指向当前执行的函数,但在严格模式下使用会报错。它是废弃特性,通常用于匿名函数递归,但现在推荐给函数命名或用箭头函数。
在箭头函数里如何获取所有参数?
箭头函数本身没有自己的 arguments 对象(它继承外层函数的 arguments,但这不是本次调用的实参),所以要用 rest 参数(...args)收集本次调用的所有参数。例如 const f = (...args) => args,args 是真实数组,可直接使用数组方法。
字符串是类数组对象吗?如何转换为数组?
字符串有索引和 length,符合类数组定义,但它有内置方法如 charAt,转换时最好用 Array.from(str) 或 [...str] 得到字符数组。注意是码点迭代,会正确处理代理对。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。