JS 深拷贝的原生终结者 structuredClone API

5,448 阅读3分钟

前言

自 JavasSript 诞生以来,我们一直无法简单地对引用数据类型进行深拷贝。现在 HTML 规范定义了structuredClone API,这是一个用于深拷贝的浏览器宿主内置函数(Node 和 Deno 也进行了实现)。借此机会,我们顺便来复习一下 JS 中的深拷贝和浅拷贝。

浅拷贝 vs 深拷贝

浅拷贝

对于 JavaScript 来说,基本上所有的拷贝方法都是浅拷贝,这也就意味着,如果原始对象的深层次数据发生改变,拷贝对象也会发生改变,因为他们保持了相同的引用,这一特性也经常引发许多 bug 。

常用浅拷贝方法

  1. ...展开操作符
const original = {a:{b:1}}
const shallowCloned = {...original} 

2.Object.assign()

const original = {a:{b:1}}
const shallowCloned = Object.assign({},original)

深拷贝

相对于浅拷贝,深拷贝对原戏象的所有层次的数据都进行了一次拷贝,原对象深层次数据发生改变,不会改变拷贝对象的值。

常用深拷贝方法

  1. JSON.parse(JSON.stringify(target)) 缺陷:1. 忽略函数;2. 忽略原型链; 3.会忽略值是 undefined 的属性;4. 当数据的层次很深,会栈溢出;
JSON.stringify({a:undefined,b:()=>{}})//🌟 '{}'
JSON.stringify(undefined) //undefined
const original = {a:{b:1}}
const deepCloned = JSON.parse(JSON.stringify(original))
  1. 手写深拷贝 —— 递归 缺陷:1. 循环引用待解决...
function getType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
function deepClone(target) {
  // 处理 原始值 null、undefined、number、string、symbol、bigInt、boolean
  if (typeof target !== 'object' || target === null) {
    return target
  }
  // 处理 array
  if (Array.isArray(target)) {
    return target.map((e) => deepClone(e))
  }
  // 处理 function
  if (getType(target) === 'Function') {
    return eval(`(${target.toString()})`).bind(this) // function 声明需要用"("、")"包裹
  }
  // 拷贝日期 
  if(getType(target) === 'Date') {
    return new Date(target.valueOf()) 
 }
  // 拷贝正则
  if(getType(target) === 'RegExp') {
    return new RegExp(target)
 }
  // 处理 map
  if (getType(target) === 'Map') {
    let map = new Map()
    target.forEach((v, k) => {
      map.set(k, deepClone(v))
    })
    return map
  }
  // 处理 set
  if (getType(target) === 'Set') {
    let set = new Set()
    for (let val of target.values()) {
      set.add(deepClone(val))
    }
    return set
  }
  // 处理 object
  if (getType(target) === 'Object') {
    let cloneTarget = {}
    for (let key in target) {
      cloneTarget[key] = deepClone(target[key])
    }
    return cloneTarget
  }
  return target
}

StructuredClone API 介绍

structuredClone结构化拷贝算法的实现,能够实现几乎对所有数据类型的深拷贝。目前最新版本的浏览器都已经原生支持。

语法

structuredClone(value)
structuredClone(value, { transfer })

参数

  • value

    想要拷贝的对象。

  • transfer (可选的)

    可转移的对象: 为一个数组,其中的值将被移动到新的对象,而不是拷贝至新的对象。

返回值

返回传入对象深拷贝之后的对象。

异常

如果传入值是不能进行深拷贝的对象,则抛出DATA_CLONE_ERR错误。

限制

  • FunctionDOM对象,如果对象中含有,将抛出DATA_CLONE_ERR异常。
  • 不保留RegExp 对象的 lastIndex 字段。
  • 不保留属性描述符,setters 以及 getters(以及其他类似元数据的功能)。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write
  • 不保留原形链。

示例

  1. 拷贝普通对象
const original = {a:{b:1}}
const cloned = structuredClone(original)
console.log(cloned) // {a:{b:1}}
console.log(origin.a===cloned.a) // false
  1. 拷贝函数
const original = {a:()=>{}}
const cloned = structuredClone(original)
// Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': ()=>{} could not be cloned.
  1. 拷贝带原型对象

image.png 原对象的原型链消失了

兼容性

目前最新版的主流浏览器都支持 structuredClone API

image.png

Firefox >= 94; Chrome(Edge) >=98; Safari >= 15.4;

可引入 core-js 兼容库 core-js#structuredclone,兼容旧版本。

引用

  1. "structuredClone" | Can I use... Support tables for HTML5, CSS3, etc
  2. structuredClone() - Web APIs | MDN (mozilla.org)