JavaScript深拷贝与浅拷贝如何实现【教程】

JavaScript浅拷贝只复制第一层属性引用,深拷贝需递归处理所有层级;JSON.parse(JSON.stringify())因丢失函数、undefined等类型而不适用于生产环境。

JavaScript 中的浅拷贝只复制对象第一层属性的引用,深拷贝则递归复制所有层级——但直接用 JSON.parse(JSON.stringify(obj)) 会丢函数、undefined、Symbol、Date、RegExp 等,生产环境必须避开这个坑。

浅拷贝:哪些方法只复制一层?

浅拷贝本质是“新对象 + 原对象顶层属性值的赋值”,对嵌套对象仍共享内存地址。

  • Object.assign({}, obj):忽略原型链上的属性,不处理 getter/setter
  • {...obj}(展开语法):同上,且无法拷贝不可枚举属性
  • Array.prototype.slice()[...arr]:仅适用于数组,对嵌套数组元素仍是浅拷贝

常见错误现象:const a = {x: {y: 1}}; const b = {...a}; b.x.y = 2; 执行后 a.x.y 也变成 2。

深拷贝:为什么 JSON.parse(JSON.stringify()) 不可靠?

它会跳过 undefinedfunctionSymbol,把 Date 变成字符串,RegExp 变成空对象,NaN 变成 null,遇到循环引用直接报错 TypeError: Converting circular structure to JSON

适用场景仅限纯数据对象(POJO),且确认不含上述类型。例如配置项、API 响应体预处理可临时用,但不能用于状态管理或通用工具函数。

手写简单深拷贝:如何处理常见边界类型?

核心思路是类型判断 + 递归,同时用 WeakMap 解决循环引用问题。

function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (map.has(obj)) return map.get(obj);
  
  const cloned = Array.isArray(obj) ? [] : {};
  map.set(obj, cloned);
  
  for (const key in obj) {
    if (O

bject.prototype.hasOwnProperty.call(obj, key)) { cloned[key] = deepClone(obj[key], map); } } return cloned; }

这个版本能处理数组、普通对象、null、基本类型,支持循环引用;但依然不处理 DateRegExpMapSet 等——如果业务需要,得在判断分支里单独处理,比如:

  • 遇到 obj instanceof Date → 返回 new Date(obj)
  • 遇到 obj instanceof RegExp → 返回 new RegExp(obj)
  • 遇到 obj instanceof Map → 遍历 obj.entries() 重建

Lodash 的 cloneDeep 和结构化克隆 API 怎么选?

现代浏览器已支持 structuredClone()(Chrome 98+、Firefox 94+、Safari 15.4+),它能正确处理 DateRegExpMapSetArrayBufferTypedArray,也支持循环引用,且比手写快。但它不支持函数、undefinedSymbol,且 Node.js 直到 v18.13.0 才默认启用(需加 --enable-structured-clone 标志)。

Lodash 的 cloneDeep 兼容性更好,但体积大(约 10KB min+gzip),若项目已引入 Lodash 可直接用;否则优先考虑 structuredClone + 特殊类型 fallback。

真正容易被忽略的是:深拷贝永远有成本。高频调用时,先想清楚是否真需要拷贝——有时用不可变更新(如 Immer)、状态分片或引用隔离更合适。