前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库arguments 转数组的方法对比
JavaScript函数与作用域

arguments 怎么转成真正的数组?几种写法有什么区别?

arguments 是类数组,有下标和 length,但原型不是 Array.prototype,没有 map 这些方法。转数组有三种写法:Array.prototype.slice.call(arguments)、Array.from(arguments)、[...arguments]。新写的函数更推荐直接用剩余参数 ...args,拿到的一开始就是数组。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

三种写法读取数据的方式不同

  1. slice.call借用数组方法,按 length 和下标逐个复制
  2. Array.from有迭代器就走迭代器,没有就按 length 和下标读
  3. 展开运算符只走 Symbol.iterator,对象没有迭代器就抛 TypeError

转出来的是新数组,改它不会影响 arguments,也不会影响形参;但数组里如果是对象,存的还是同一个对象的引用。

核心回答

先记住这个答案

arguments 是一个类数组对象:有数字下标、有 length,但它的原型是 Object.prototype,所以没有 map、filter、reduce。常见的转换写法有三种。Array.prototype.slice.call(arguments) 是 ES5 时代的老写法,借用数组的 slice 方法。Array.from(arguments) 和 [...arguments] 是 ES6 之后的写法,更直观。如果函数是自己新写的,直接声明剩余参数 function f(...args),args 本身就是数组,不需要转换。

  • arguments 是类数组,没有 map、filter
  • Array.from 同时支持类数组和可迭代对象
  • 展开运算符只认迭代器,普通类数组会报错
  • 新代码直接用剩余参数 ...args

三种写法都能用在 arguments 上

先确认 arguments 确实不是数组,再看三种写法的结果。

同一组实参,三种转换JavaScript
function collect() {
  console.log(Array.isArray(arguments), typeof arguments.map) // 不是数组,没有 map

  const byFrom = Array.from(arguments)
  const bySpread = [...arguments]
  const bySlice = Array.prototype.slice.call(arguments)

  console.log([byFrom, bySpread, bySlice].every(Array.isArray)) // 三个都是真数组
  console.log(byFrom.map((n) => n * 2).join(','))
}

collect(1, 2, 3)
查看输出与解释
false undefined
true
2,4,6

slice.call 能成功,是因为数组的很多方法是「通用」的:它们内部只读 this.length 和 this[0]、this[1] 这些下标,不要求 this 真的是数组。用 call 把 this 换成 arguments,slice() 不传参数就复制出一个完整的新数组。同样的道理,也可以不转换,直接借用:Array.prototype.map.call(arguments, fn)。 [...arguments] 能成功,是因为 ES6 给 arguments 对象加上了 Symbol.iterator,它是可迭代的。这一点很关键,下一节会看到,普通的类数组对象就没有这个待遇。

类数组和可迭代对象不是一回事

这是三种写法真正的区别。类数组指有 length 和数字下标的对象;可迭代对象指实现了 Symbol.iterator 方法的对象。arguments、NodeList 两者都是,但你自己写的 { 0: 'a', length: 1 } 只是类数组。

普通类数组对象JavaScript
const likeArray = { 0: 'a', 1: 'b', length: 2 } // 有下标和 length,没有迭代器

console.log(Array.from(likeArray).join(',')) // 按 length 和下标读取
console.log(Array.prototype.slice.call(likeArray).join(',')) // 同样按下标读取
try {
  console.log([...likeArray])
} catch (e) {
  console.log(e.name) // 展开只认迭代器
}
console.log(Array.from({ length: 3 }, (_, i) => i * i).join(',')) // 第二个参数做映射
查看输出与解释
a,b
a,b
TypeError
0,1,4

Array.from 的逻辑是先看对象有没有 Symbol.iterator,有就用迭代器,没有就退回到按 length 和下标读取。所以它两种对象都能处理。展开运算符只走迭代器,遇到没有迭代器的普通对象,直接抛 TypeError,提示对象不可迭代。 Array.from 的第二个参数是映射函数,相当于转换完再 map 一次,但少建一个中间数组。Array.from({ length: n }, fn) 也常被用来生成指定长度的数组。

空位的处理也不一样
Array.from 会把缺失的下标读成 undefined,得到一个没有空位的数组。slice.call 则会保留空位:Array.prototype.slice.call({ length: 2 }) 得到的数组里是两个空位,对它判断 1 in arr 会得到 false,用 forEach 遍历时空位会被跳过。转换 arguments 遇不到这种情况,但转换自定义的类数组时要知道。

新代码直接用剩余参数

arguments 在非严格模式下还有一个特别的行为:它和形参是联动的。改 arguments[0],第一个形参也跟着变。严格模式下这种联动取消了,转出来的数组更是和形参完全无关。

arguments 联动、转换后的数组和剩余参数JavaScript
function sloppy(a) {
  arguments[0] = 99 // 非严格模式:会同时改掉 a
  return a
}
function copied(a) {
  const args = Array.from(arguments)
  args[0] = 99 // 改的是新数组,和 a 无关
  return a
}
function withRest(first, ...rest) {
  return [arguments.length, rest.length, Array.isArray(rest)].join(' ')
}

console.log(sloppy(1), copied(1))
console.log(withRest(1, 2, 3)) // rest 只收剩下的实参
查看输出与解释
99 1
3 2 true

剩余参数有三个好处。第一,它本身就是真数组,不用再转换。第二,它只收集没有对应形参的那部分实参,上面的 rest 只有 2 和 3,而 arguments 永远包含全部实参。第三,箭头函数没有自己的 arguments,但可以用剩余参数。所以现在写新函数,基本不需要再碰 arguments。

回答前,多想一步

容易答错的地方

直接调用 arguments.map
arguments 虽然长得像数组,也能用 for 循环和 for...of 遍历,但原型上没有数组方法,arguments.map(...) 会抛 TypeError,提示 map 不是函数。
以为展开运算符能处理所有类数组
展开能用在 arguments、NodeList、Set、字符串上,是因为它们都可迭代,不是因为它们是类数组。接口返回的 { 0: ..., length: n } 这种对象,只能用 Array.from 或 slice.call。
以为转换后是深拷贝
三种写法都只复制了一层。实参如果是对象,新数组里存的还是同一个对象的引用,改对象的属性,原来持有它的地方都能看到。
试着用自己的话回答

面试官还会怎么问?

为什么很多教程说不要把 arguments 传给别的函数?

早期的 V8 在函数把 arguments 泄漏出去时,比如 return arguments 或者直接传给其他函数,会放弃对这个函数的优化,所以当时的性能建议是在函数内部用 for 循环手动复制。新版引擎已经大幅改善了这个问题,但剩余参数依然是最干净的写法。

arguments.length 和函数的 length 有什么区别?

arguments.length 是这次调用实际传入的实参个数。函数的 length 是声明的形参个数,而且不算剩余参数,也不算有默认值的参数及其后面的参数。比如 function f(a, b = 1, ...c) {} 的 f.length 是 1。

NodeList 和 HTMLCollection 转数组也一样吗?

思路一样,但要注意差别。document.querySelectorAll 返回的 NodeList 可迭代,本身还自带 forEach;getElementsByTagName 返回的 HTMLCollection 是实时更新的集合,现代浏览器里它也可迭代。遍历过程中如果会增删节点,最好先用 Array.from 转成静态数组再操作。

从一道题,走向一组知识

把知识连起来

函数与作用域

剩余参数 ...args 和 arguments 有什么区别?为什么推荐用前者?

剩余参数从函数入口就拿到真数组,是 arguments 转数组这个问题在新代码里的标准答案。

函数与作用域

箭头函数里读 arguments,读到的是谁的?

箭头函数里根本没有自己的 arguments,只能用剩余参数,这是转数组之外更常见的坑。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • The arguments object

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 三种写法都能用在 arguments 上
  3. 类数组和可迭代对象不是一回事
  4. 新代码直接用剩余参数
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑