精通JavaScript拷贝需建立三层认知:内存模型直觉、场景决策能力、边界预判力;赋值对原始类型是拷贝,对引用类型只是地址复制;浅拷贝适用表单编辑等场景,可靠写法含展开运算符和Object.assign;深拷贝须考量函数/Symbol/循环引用/特殊对象,推荐structuredClone;手写需涵盖类型识别、循环检测、特殊构造与递归控制。

要真正精通 JavaScript 中的浅拷贝与深拷贝,关键不是记住多少种写法,而是建立三层认知:内存模型直觉、场景决策能力、边界问题预判力。下面从四个实战维度展开,全是工程中反复验证过的要点。
看懂“赋值”和“拷贝”的本质区别
很多人卡在第一步——分不清变量赋值(=)和对象拷贝(如 {...obj})。这不是语法问题,是内存理解问题。
- 原始类型(string/number/boolean/null/undefined/Symbol/BigInt):赋值即拷贝,两个变量完全独立
- 引用类型(Object/Array/Date/RegExp/Map/Set 等):赋值只是复制地址,两个变量指向同一块堆内存
- 所谓“拷贝”,就是主动创建新对象;而“浅”或“深”,只取决于嵌套层级是否也被新建
浅拷贝不是“不够好”,而是“用对地方才高效”
浅拷贝不是过渡方案,它在多数业务场景中反而是最优解——快、轻、可预测。
- 适用场景:表单临时编辑、React props 透传、状态快照(仅顶层变化)、配置合并(
Object.assign({}, defaults, overrides)) - 可靠写法(按推荐顺序):
-
{...obj}—— 语义清晰,支持解构重命名,但跳过undefined和不可枚举属性 -
Object.assign({}, obj)—— 兼容性好,可链式合并多个源,但不处理Symbol键 -
structuredClone(obj, { transfer: [] })(仅需浅层时慎用,实际是深拷贝 API,但了解其存在有助于判断替代方案)
-
- 陷阱提醒:数组里含对象时,
[...arr]仍是浅拷贝;arr.slice()对稀疏数组会补undefined
深拷贝必须问清三个问题,再选方案
没有“万能深拷贝”。每次要用前,快速自问:
-
数据里有没有函数、undefined、Symbol、BigInt? → 排除
JSON.parse(JSON.stringify()) -
会不会出现循环引用?(比如
obj.parent = obj)→ 排除所有无缓存机制的递归实现 -
是否需要保留特殊对象实例?(如
Date要保持类型,不能变字符串;Map要保留键值对顺序)→ 需手写或用structuredClone
当前最稳妥选择:
浏览器环境优先用 structuredClone()(已正式标准化,2025年起全主流浏览器支持),它原生支持 Map/Set/Date/RegExp/ArrayBuffer/Transferable,且自动处理循环引用。
Node.js 环境可用 node:util 的 structuredClone(v17.0+)或 lodash.cloneDeep(兼容性兜底)。
手写深拷贝不必从零造轮子,但必须掌握核心骨架
哪怕项目中直接用库,也建议手写一个最小可行版,用于理解原理和调试边界。
- 核心四要素缺一不可:
- 类型识别(
Array.isArray、Object.prototype.toString.call) - 循环引用检测(用
WeakMap缓存源对象 → 拷贝对象映射) - 特殊对象构造(
new Date()、new RegExp()、new Map()等) - 递归入口控制(基本类型直接返回,引用类型才进递归)
- 类型识别(
- 一个精简可靠的起点(支持常见类型,不含 Map/Set):
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj);
const cloned = Array.isArray(obj) ? [] : {};
cache.set(obj, cloned);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], cache);
}
}
return cloned;
}


















