前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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对象与原型

{ ...obj } 会复制哪些属性?不可枚举的、原型上的会被复制吗?

{ ...obj } 只复制 obj 自身的、可枚举的属性,Symbol 键也算。原型链上的属性和不可枚举的属性都不会复制。遇到 getter 会先执行它,把返回值作为普通属性存进新对象。

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

一个属性能不能被展开,看三个条件

  1. 自身属性必须是对象自己的属性,从原型继承来的不算
  2. enumerable: true必须可枚举,defineProperty 默认定义的属性就是不可枚举的
  3. 结果是数据属性复制到新对象后一律是可写、可枚举、可配置的普通属性

展开是在新对象上「定义」属性,不是「赋值」。所以目标对象上的 setter 不会被触发,这是它和 Object.assign 最容易被忽略的区别。

核心回答

先记住这个答案

对象展开只复制源对象自身的、可枚举的属性。字符串键和 Symbol 键都会复制,但原型链上继承来的属性不复制,用 Object.defineProperty 定义的不可枚举属性也不复制。如果属性是 getter,展开时会调用一次 getter,把返回值作为普通数据属性放进新对象,getter 本身不会被带过去。另外它是浅拷贝,嵌套对象还是同一个引用。

  • 只复制自身的可枚举属性,Symbol 键也复制
  • 原型链上的属性、不可枚举属性都不复制
  • getter 会被执行,新对象里存的是值
  • 类实例展开后方法会丢,因为方法在原型上

把各种属性放在一起展开一次

构造一个同时有继承属性、不可枚举属性、Symbol 属性和 getter 的对象,看展开后留下了哪些。

展开前后的属性对比JavaScript
const src = Object.create({ inherited: 1 }) // 原型上有 inherited
src.own = 2
Object.defineProperty(src, 'hidden', { value: 3 }) // 默认不可枚举
src[Symbol('id')] = 4
Object.defineProperty(src, 'computed', {
  enumerable: true,
  get() { console.log('getter 执行'); return 5 }
})

const copy = { ...src }
console.log(Object.keys(copy).join(','))
console.log('inherited' in copy, 'hidden' in copy)
console.log(Object.getOwnPropertySymbols(copy).length)
console.log(JSON.stringify(Object.getOwnPropertyDescriptor(copy, 'computed')))
查看输出与解释
getter 执行
own,computed
false false
1
{"value":5,"writable":true,"enumerable":true,"configurable":true}

第一行说明展开的那一刻 getter 就被调用了。inherited 在原型上,hidden 不可枚举,两个都没过来。Symbol 键复制成功了,这点和 Object.keys、for...in 不一样,它们都会跳过 Symbol。最后一行能看到 computed 在新对象里已经是一个值为 5 的普通属性,以后原对象的数据怎么变,它都不会再更新。

为什么类实例展开后方法没了

这是项目里最常踩的坑。比如有一个 class User,实例上有 name,类里定义了 getName() 方法。{ ...user } 之后,name 还在,调用 copy.getName() 却报错 TypeError。

原因是类里写的方法都挂在 User.prototype 上,不是实例自己的属性,展开时不会复制。新对象的原型是普通的 Object.prototype,所以 copy instanceof User 也是 false。用 getter 定义的计算属性同理,它们在原型上,展开后直接消失,连值都没有。

所以 Redux、React 的 state 里通常只放普通对象。放了类实例,每次用展开写不可变更新,就会悄悄把它变成普通对象。

想完整复制一个对象怎么办
要连原型、不可枚举属性、getter 一起保留,用描述符来复制:Object.create(Object.getPrototypeOf(src), Object.getOwnPropertyDescriptors(src))。getOwnPropertyDescriptors 会拿到所有自身属性的完整描述,getter 以函数的形式保留,不会被执行。
回答前,多想一步

容易答错的地方

以为展开和 Object.assign 完全一样
它们复制的属性范围相同,都是自身可枚举属性。区别在写入方式:Object.assign(target, src) 是对 target 赋值,如果 target 上有同名 setter 会被触发;展开是直接在新对象上定义属性,不触发 setter。另外 Object.assign 会修改第一个参数,展开总是返回新对象。
以为展开 null 会报错
{ ...null } 和 { ...undefined } 都返回空对象,不会报错。这和数组展开不同,[...null] 会抛 TypeError,因为数组展开要求值可迭代。
以为数组的 length 会被复制
{ ...[1, 2] } 得到 { 0: 1, 1: 2 },没有 length,因为数组的 length 是不可枚举的。
试着用自己的话回答

面试官还会怎么问?

属性同名时谁覆盖谁?

后面的覆盖前面的。{ ...defaults, ...options } 里 options 的值会覆盖 defaults,所以默认值要写在前面。

展开字符串会得到什么?

{ ...'hi' } 得到 { 0: 'h', 1: 'i' }。字符串被包装成对象后,每个字符都是一个可枚举的索引属性。数字、布尔值展开后是空对象。

展开会复制属性的顺序吗?

会按源对象的属性顺序复制:先是整数键按从小到大,再是字符串键按添加顺序,最后是 Symbol 键。

从一道题,走向一组知识

把知识连起来

对象与原型

class 的静态方法会被子类继承吗?子类里怎么调用父类的静态方法?

展开类实例会丢掉原型上的方法,而静态方法是挂在类本身上的,两题一起能把「方法到底放在哪」弄清楚。

对象与原型

怎么用 Symbol.toStringTag 改变 Object.prototype.toString 的输出?

Symbol.toStringTag 常定义在原型上的 getter 里,展开对象时不会被带走,拷贝后的类型标签就变回 Object。

参考资料

  • JavaScript:对象与原型参考资料

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

本题目录
  1. 先记住这个答案
  2. 把各种属性放在一起展开一次
  3. 为什么类实例展开后方法没了
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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