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