前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库展开运算符 数组 迭代器机制
JavaScript数组与集合

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

[...x] 会调用 x[Symbol.iterator]() 拿到迭代器,一直调 next() 把每个值收集进新数组。数组、Set、Map、字符串都实现了迭代器,所以都能展开。它复制的是迭代器吐出来的值,元素是对象时只复制引用,是浅拷贝。

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

`[...x]` 分三步完成

  1. 取迭代器调用 x[Symbol.iterator](),没有这个方法就抛 TypeError
  2. 逐个取值反复调用 next(),直到返回的 done 为 true
  3. 写入新数组把每个 value 按顺序放进新数组,对象只放引用

只有数字键和 length 的类数组对象,比如 { 0: 'a', length: 1 },没有迭代器,数组展开会报错,要用 Array.from 转换。

核心回答

先记住这个答案

数组字面量里的 ...x 走的是迭代器协议:先调用 x[Symbol.iterator]() 拿到一个迭代器,然后反复调用它的 next(),把每次返回的 value 依次放进新数组,直到 done 为 true。数组的默认迭代器按索引输出元素,所以展开看起来就是复制数组;Set 的迭代器按插入顺序输出成员,Map 输出 [key, value] 键值对,字符串按字符输出。新数组是一个独立的容器,但里面的对象还是原来那些,所以是浅拷贝。对象展开 {...obj} 虽然写法很像,但它复制的是对象自己的可枚举属性,和迭代器没有关系。

  • 数组展开依赖 Symbol.iterator,不是按索引复制
  • 能展开的有数组、Set、Map、字符串和 arguments
  • 展开是浅拷贝,对象元素和原数组共享
  • 对象展开 {...obj} 复制属性,和迭代器无关

用两个例子看清展开读的是什么

第一个例子验证浅拷贝,第二个例子给数组换一个迭代器,看展开的结果会不会跟着变。

浅拷贝和自定义迭代器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

第一行:copy 和 source 是两个数组,但 copy[0] 和 source[0] 是同一个对象,所以改了副本里的 count,原数组也变成了 2。 第二个例子更能说明问题:custom 的索引 0 上存的是 'stored',但它的迭代器被换成了只产出 'yielded' 的生成器函数,展开得到的就是 ["yielded"]。展开根本没有去读索引,它只认迭代器。实际项目里不要这样改数组的迭代器,这里只是为了演示原理。

数组展开和对象展开是两回事

[...set] 调用 Set 的迭代器,得到成员组成的数组。{...set} 却是按对象属性复制,Set 的成员不是它的属性,所以得到一个空对象 {}。

对数组用对象展开
{...['a', 'b']} 会得到 { 0: 'a', 1: 'b' },一个以索引为键的普通对象,没有 length,也不能调用数组方法。反过来,普通对象不能放进数组展开,[...{ a: 1 }] 会抛 TypeError,提示对象不可迭代。 所以记住一点:方括号里的展开找迭代器,花括号里的展开找属性。
回答前,多想一步

容易答错的地方

把数组展开当深拷贝
展开只复制了一层。数组里是对象或嵌套数组时,副本和原数组共享它们,改嵌套内容两边都会变。要深拷贝用 structuredClone,或者只复制要修改的那一层。
以为有数字索引和 length 就能展开
document.querySelectorAll 返回的 NodeList 能展开,是因为它实现了迭代器,不是因为它有 length。自己写的 { 0: 'a', length: 1 } 没有迭代器,展开会报错,要用 Array.from 转成数组。
试着用自己的话回答

面试官还会怎么问?

展开 Map 得到什么?

得到键值对组成的二维数组,比如 [['a', 1], ['b', 2]]。只要键就展开 map.keys(),只要值就展开 map.values()。

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

不在了。数组迭代器会按索引逐个读,空位读到的是 undefined,写进新数组后变成真实存在的 undefined。所以 [...arr] 会把稀疏数组变成稠密数组,而 arr.slice() 会保留空位。

展开一个很大的数组或者无限迭代器会怎样?

展开会一口气读完迭代器的所有值,数据很大时会占用大量内存。遇到无限迭代器,比如一个不停 yield 的生成器,展开永远不会结束。另外,函数调用里的展开 fn(...arr) 还受参数个数上限的限制,超大数组可能报错,求最大值这类场景用 reduce 更稳。

从一道题,走向一组知识

把知识连起来

数组与集合

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

Array.from 既能接收可迭代对象,也能接收类数组对象,正好补上展开做不到的那种情况。

数组与集合

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

fill 填对象时每个位置共享同一个引用,和展开只复制对象引用是同一个问题。

数组与集合

for...of 和 forEach 怎么跳过当前项、提前结束,异步时有什么区别?

for...of 和数组展开用的是同一个迭代器,那篇讲 for...of 怎么提前退出和处理异步。

参考资料

  • MDN:展开语法
  • MDN:Array

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

本题目录
  1. 先记住这个答案
  2. 用两个例子看清展开读的是什么
  3. 数组展开和对象展开是两回事
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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