标准答案

  1. 浅拷贝:会创建一个新对象或新数组,但只复制第一层属性。嵌套对象、数组、Map 等引用值仍然和原数据指向同一份内存,所以修改嵌套内容会影响原对象。
  2. 常见浅拷贝:Object.assign、对象展开、数组展开、slice、concat 都是浅拷贝。它们适合复制一层普通对象或数组,不适合直接复制复杂对象图。
  3. 深拷贝:需要递归复制嵌套引用,目标是让新对象和原对象的内部引用也彼此独立。手写深拷贝时要处理循环引用、Date、RegExp、Map、Set、数组、原型、Symbol 键等边界。
  4. 方案取舍:JSON.parse(JSON.stringify(obj)) 只能处理简单 JSON 数据,会丢失 undefined、function、symbol、Date、RegExp、Map、Set、循环引用等。structuredClone 能处理更多内置类型和循环引用,但不能克隆函数、DOM 节点。业务里优先根据数据结构选择方案,不要默认手写万能深拷贝。

题目解析

这里要先把“复制值”和“复制引用”说清楚。浅拷贝不是没有拷贝,而是只拷贝容器第一层;第一层里的对象仍然是同一份引用,所以问题通常出现在嵌套表单、配置对象、列表项对象被修改时。

Object.assign 和展开运算符的价值是简单、直观、适合一层数据合并;它们的边界是不会递归处理嵌套结构,也不会自动处理不可枚举属性、访问器属性、原型链和特殊对象。它们适合普通业务对象的一层复制,不适合直接处理完整对象图。

structuredClone 更接近浏览器提供的通用深拷贝能力,能处理循环引用和不少内置类型,但它不是万能序列化工具,函数、DOM 节点、WeakMap 等仍然不能克隆。JSON 方案更窄,只适合纯 JSON 数据,类型丢失是它最大的风险。

如果需要自己实现深拷贝,核心是用 WeakMap 记录已经复制过的对象来处理循环引用,再按 Date、RegExp、Map、Set、数组、普通对象等类型分别处理。手写实现属于进一步展开的问题,不影响这里先判断现成 API 的适用边界。

代码示例

下面这段代码可以直接说明浅拷贝共享嵌套引用,以及 structuredClone 的适用边界。

JavaScript
const source = {
  name: 'form',
  config: { visible: true }
}

const shallow = { ...source }
shallow.config.visible = false

console.log(source.config.visible) // false,嵌套对象仍然共享引用

const cloned = structuredClone({
  createdAt: new Date(),
  list: [{ id: 1 }],
  self: null
})

// structuredClone(() => {}) // DataCloneError,函数不能被结构化克隆

常见误区

  • 把展开运算符当成深拷贝。
  • 认为 JSON.parse(JSON.stringify()) 能复制所有数据。
  • 手写深拷贝时没有用 WeakMap 处理循环引用。

作者信息