532 字
3 分钟
你还在用JSON.stringify深度拷贝对象吗
当对象进行深度拷贝时,一种常见的方法是使用现有的库或编写递归函数来执行复制操作,这些方法可能相对繁琐。许多人会更倾向于使用 JSON.parse/JSON.stringify 来快速实现,但这种方法存在一些缺陷和潜在错误。接下来,我将分析另外两种更好的深度拷贝方式。
作为对比,首先准备了如下测试数据,尽量类型覆盖全面。
const obj = { // 基本数据类型 num: 1, str: 'abc', bool: true, nul: null, undf: undefined, sym: Symbol('1'), // bign: BigInt(1n), // 普通对象 obj: { name: '名称', age: 20, }, // 数组 arr: [1, 2, 3], // 函数 func: function () { console.log('function') }, // 时间 date: new Date(), // 正则 reg: /[0-9]/, // Map map: new Map().set('key', 1), // Set set: new Set().add('set'), // Symbol为key [Symbol('key')]: 1, } // 不可枚举属性 Object.defineProperty(obj, 'inumerable', { enumerable: false, value: '不可枚举属性', }) // 原型对象 Object.setPrototypeOf(obj, { proto: 'proto', }) // 循环引用 obj.loop = obj
JSON.parse/JSON.stringify
JSON.parse/JSON.stringify
是最常用的深度拷贝方法。
JSON.parse(JSON.stringify(obj))
通过测试,发现如下问题:
- 执行会报错,存在 BigInt 类型、循环引用;
Date
类型会变成字符串;- 键值丢失:值为
Function
、undefined
、Symbol
类型; - 键名丢失:键名为
Symbol
类型; - 键值变成空对象:值为
Map
、Set
、RegExp
类型; - 无法拷贝:不可枚举属性、对象原型链。
MessageChannel
MessageChannel
是用来创建一个消息通道的,可以通过 2 个管道的消息发送和接收进行对象拷贝。
function deepClone(obj) { return new Promise((resolve) => { const MC = new MessageChannel() const port1 = MC.port1 const port2 = MC.port2 port1.onmessage = (e) => { resolve(e.data) } port2.postMessage(obj) }) } deepClone(obj).then((res) => { console.log(res) })
通过测试发现:
- 执行会报错,值为
Function
、Symbol
类型; - 键名丢失:键名为
Symbol
类型; - 无法拷贝:不可枚举属性、对象原型链。
虽然有一些问题,但整体比JSON.parse/JSON.stringify
结果要好。看下兼容性,大部分主流浏览器都是兼容:
structuredClone
structuredClone
是一个新的深度拷贝方法,它的效果和MessageChannel
一样,并且使用起来更简单,结果也是同步返回:
structuredClone(obj)
但是它的兼容性还不太理想:
你还在用JSON.stringify深度拷贝对象吗
https://fuwari.vercel.app/posts/20230912/