多多读书
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))

通过测试,发现如下问题:

  1. 执行会报错,存在 BigInt 类型、循环引用;
  2. Date类型会变成字符串;
  3. 键值丢失:值为FunctionundefinedSymbol类型;
  4. 键名丢失:键名为Symbol类型;
  5. 键值变成空对象:值为MapSetRegExp类型;
  6. 无法拷贝:不可枚举属性、对象原型链。

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) })

通过测试发现:

  1. 执行会报错,值为FunctionSymbol类型;
  2. 键名丢失:键名为Symbol类型;
  3. 无法拷贝:不可枚举属性、对象原型链。

虽然有一些问题,但整体比JSON.parse/JSON.stringify结果要好。看下兼容性,大部分主流浏览器都是兼容:

image.png

structuredClone#

structuredClone 是一个新的深度拷贝方法,它的效果和MessageChannel一样,并且使用起来更简单,结果也是同步返回:

structuredClone(obj)

但是它的兼容性还不太理想:

image.png

你还在用JSON.stringify深度拷贝对象吗
https://fuwari.vercel.app/posts/20230912/
作者
我也困了
发布于
2023-09-12
许可协议
CC BY-NC-SA 4.0