javascript怎样实现深拷贝_有哪些常用方法【教程】

8次阅读

javaScript无完美深拷贝方案:jsON.parse(json.stringify())丢函数/undefined/symbol/date/regexp/map/Set且不支持循环引用;structuredClone()支持现代环境的多数类型但不支持函数/undefined/Symbol;手写递归需处理循环引用和类型判断;Lodash cloneDeep功能全面但体积大且5.0将移除。

javascript怎样实现深拷贝_有哪些常用方法【教程】

javascript 没有原生的、完美通用的深拷贝函数,JSON.parse(JSON.stringify(obj)) 看似简单,但会丢函数、undefined、Symbol、Date、RegExp、Map、Set、循环引用等;直接用结构赋值或 Object.assign() 只是浅拷贝。

为什么 JSON.parse(JSON.stringify()) 不可靠

它本质是“序列化再反序列化”,只处理可 JSON 化的值:

  • functionundefinedSymbol 会被忽略或转成 NULL
  • Date 变成字符串RegExp 变成空对象 {}
  • Map/Set 直接丢失(JSON 不支持)
  • 遇到循环引用会抛错:TypeError: Converting circular structure to JSON
  • 原型链、getter/setter 全部丢失

示例:

const obj = { d: new Date(), r: /abc/, m: new Map([[1, 'a']]) }; JSON.parse(JSON.stringify(obj)); // { d: "2024-01-01T00:00:00.000Z", r: {}, m: undefined }

structuredClone()(现代浏览器首选)

这是目前最接近“标准深拷贝”的方案,支持 DateRegExpMapSetArrayBufferTypedArrayBigInt 和循环引用,且保留类型语义。

  • 仅限浏览器chrome 98+、firefox 94+、edge 98+)和 node.js 17.0+(需启用 --experimental-structured-cloningnode.js 18.15+ 默认开启)
  • 不支持函数、undefinedSymbol —— 这是设计限制,不是 bug
  • 不能拷贝带有不可枚举属性或自定义 getter 的对象(只处理可枚举 + 可克隆的数据)

用法很简单:

const copy = structuredClone(originalObj);

立即学习Java免费学习笔记(深入)”;

手写递归深拷贝(兼容性要求高时)

适用于需要支持老环境(如 IE)、或需定制行为(比如跳过某些字段、处理特殊类实例)的场景。关键点:

  • 必须检测循环引用,用 WeakMap 缓存已拷贝的源对象
  • 区分基本类型、null、数组、普通对象、DateRegExpMapSet 等,分别处理
  • 避免用 typeof 判数组(返回 "object"),改用 Array.isArray()
  • 不要用 for...in 遍历对象 —— 它会遍历原型链;应使用 Object.keys()Object.getOwnPropertyNames()

最小可用骨架示例:

function deepClone(obj, seen = new WeakMap()) {   if (obj === null || typeof obj !== 'object') return obj;   if (seen.has(obj)) return seen.get(obj);      let clone;   if (obj instanceof Date) clone = new Date(obj);   else if (obj instanceof RegExp) clone = new RegExp(obj);   else if (Array.isArray(obj)) clone = obj.map(item => deepClone(item, seen));   else if (obj instanceof Map) clone = new Map([...obj].map(([k, v]) => [k, deepClone(v, seen)]));   else if (obj instanceof Set) clone = new Set([...obj].map(v => deepClone(v, seen)));   else {     clone = Object.create(Object.getPrototypeOf(obj));     seen.set(obj, clone);     for (const key of Object.getOwnPropertyNames(obj)) {       const desc = Object.getOwnPropertyDescriptor(obj, key);       Object.defineProperty(clone, key, {         value: deepClone(desc.value, seen),         enumerable: desc.enumerable,         writable: desc.writable,         configurable: desc.configurable       });     }   }   return clone; }

Lodash 的 cloneDeep() 是什么定位

它不是“银弹”,而是权衡后的实用方案:

  • 覆盖绝大多数常见类型(包括函数、undefinedSymbolErrorpromise 等),但对自定义类实例默认只做浅拷贝(除非你显式定义 clone 方法)
  • 内部做了大量边界处理(如稀疏数组、负索引、不可枚举属性),比手写更健壮
  • 体积不小(约 20KB minified),如果项目已用 Lodash 且其他功能也在用,值得引入;否则为单个功能引入略重
  • 注意:Lodash 5 将移除 cloneDeep,推荐迁移到 lodash-es 的按需导入或转向 structuredClone

用法:

import { cloneDeep } from 'lodash-es'; const copy = cloneDeep(originalObj);

真正要选哪种方式,取决于你的运行环境、数据结构复杂度、是否允许丢弃某些值(比如函数),以及是否愿意承担额外依赖。别只看“能不能拷”,得看“拷完还是不是原来那个东西”。

text=ZqhQzanResources