前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库展开运算符 数组 迭代器机制
JSJavaScript数组与集合

数组展开为什么能复制数组和 Set,它到底读取了什么?

展开复制的是迭代器产生的一串值,不是把源对象内部结构原封不动搬过去。

前端进阶之旅 · 一题精讲更新于 2026.09.06
JavaScript#数组与集合
先看核心答案读代码示例
理解线索

从数据源到新数组追踪三步

  1. 取得迭代器输入需要提供可调用的同步迭代方法
  2. 逐项读取按照 next 产生的顺序收集 value
  3. 写入容器新数组保存这些值与对象引用

普通数组默认行为只是常见情况。自定义迭代器、访问器或代理都可能改变读取结果与副作用,复制前应确认输入契约。

核心回答

先记住这个答案

数组字面量中的 ... 会取得输入的同步迭代器,连续读取 next 的结果并写入新数组。普通数组默认迭代器按索引产生值,所以看起来像复制数组;Set 默认按插入顺序产生成员,Map 默认产生键值对。新数组是独立容器,但对象元素仍共享引用。若改写数组迭代器,展开结果也会改变;普通空位通常被读取为 undefined 后写成实际位置。对象字面量的 ... 则复制自有可枚举属性,不能把这两种同形语法当作同一读取算法。

  • 数组展开消费同步迭代器产生的值
  • 容器复制与元素对象复制是两层事情
  • 对象展开按属性读取而非按迭代序列

浅复制可以通过对象身份直接验证

下面先复制包含对象的数组,然后修改副本中对象的属性。数组身份不同,但对象身份仍相同,因此源数组看到相同的属性变化;这说明新容器并没有自动提供深层数据隔离。

示例再给一个数组安装不同的迭代器,使展开产生替代值。它用于解释协议依赖,不建议为了隐藏数据而改写普通数组行为;这种自定义必须在接口上明确,否则消费者会很难排查。

对象引用与自定义迭代结果JavaScript
const source = [{ count: 1 }];
const copy = [...source];
copy[0].count = 2;
console.log(copy === source, copy[0] === source[0], source[0].count);
const custom = ['stored'];
custom[Symbol.iterator] = function* () { yield 'yielded'; };
console.log(JSON.stringify([...custom]));
console.log(custom[0]);
查看输出与解释
false true 2
["yielded"]
stored

副本数组独立但元素对象共享。展开自定义数组得到 yielded,而索引零仍存 stored,直接证明展开读取迭代序列而非绕过协议复制所有索引。

同样三个点在对象里有不同规则

把 Set 写成 [...set] 会消费它的成员迭代器;写成 {...set} 则按对象属性复制,普通 Set 的集合条目不是自有可枚举字符串属性,因此不会变成想象中的成员记录。

数组对象展开得到的是索引属性构成的普通对象,也不会因此拥有数组原型和 length 语义。需要什么容器应通过 API 明确表达,不能只因为语法外观相同就期待返回同一种数据结构。

一次性物化要考虑容量和终止

展开会持续消费迭代器直到结束,对无限序列会无法正常完成,对很大的输入会一次性分配结果。若只要部分数据,应使用有界循环或分页消费,避免把惰性数据源整体拉进内存。

函数参数展开还受到调用参数数量等实现限制,与数组字面量展开不应混为一谈。对于共享状态,是否需要复制嵌套对象也必须单独决定;只有容器新引用并不能保证后续更新互不影响。

回答前,多想一步

容易答错的地方

用数组展开当作通用深拷贝
展开保存的是原有元素值,对象值仍是引用,嵌套修改可能影响两边。应根据要修改的路径复制必要层级,或选择符合数据类型与性能要求的克隆方案,不能用三个点代替隔离设计。
认为任何有数字属性的对象都能展开进数组
数组展开要求可迭代协议,只有数字键和 length 的类数组可能没有这个能力。可以按明确类数组规则使用 Array.from,或为对象实现迭代器,不能把属性外观当作协议成立。
试着用自己的话回答

面试官还会怎么问?

展开 Map 得到的是值数组吗?

默认 Map 迭代器产生键值对,因此结果通常是由二元数组组成的数组。若只需要值,应显式展开 map.values;选择 keys、values 或 entries 能让读者直接理解希望保留的信息。

展开稀疏数组后空位还存在吗?

普通数组迭代器会读取这些位置,未找到属性时得到 undefined,再写入新数组,所以通常成为显式值。若原型上存在同名索引属性,读取结果还可能来自原型,不能忽略对象属性查找。

复制后原数组追加元素会同步出现吗?

不会,展开完成后的新数组长度与元素位置已经建立,后续追加不会自动同步容器结构。但两边已有对象元素仍可共享内部变化,因此应分别讨论结构快照和对象状态快照。

从一道题,走向一组知识

把知识连起来

数组与集合

Array.from 为什么能转换类数组,而数组展开有时会报错?

对照支持类数组输入的转换路径与迭代路径。

数组与集合

Array.fill 的负数 start、end 怎样换算,为什么最后一项没有被填?

理解写入同一对象值与复制对象引用的共同边界。

数组与集合

for...of 与 forEach 怎样跳过当前项、提前结束和处理异步任务?

比较循环消费迭代器时的退出与任务等待。

参考资料

  • MDN:展开语法
  • MDN:Array

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

本题目录
  1. 先记住这个答案
  2. 浅复制可以通过对象身份直接验证
  3. 同样三个点在对象里有不同规则
  4. 一次性物化要考虑容量和终止
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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