IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    JavaScript之structuredClone现代深拷贝

    南城FE发表于 2024-03-04 10:25:28
    love 0

    在JavaScript中,实现深拷贝的方式有很多种,每种方式都有其优点和缺点。今天介绍一种原生JavaScript提供的structuredClone实现深拷贝。

    下面列举一些常见的方式,以及它们的代码示例和优缺点:

    1. 使用JSON.parse(JSON.stringify(obj))

    代码示例:

    function deepClone(obj) {
        return JSON.parse(JSON.stringify(obj));
    }

    优点:简单易行,对于大多数对象类型有效。

    缺点:不能复制原型链,对于包含循环引用的对象可能出现问题。比如以下代码:

    const calendarEvent = {
      date: new Date()
    }
    
    const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

    最终得到的date不是Data对象,而是字符串。

    {
        "date": "2024-03-02T03:43:35.890Z"
    }

    这是因为JSON.stringify只能处理基本的对象、数组。任何其他类型都没有按预期处理。例如,日期转换为字符串。Set/Map只是转换为{}。

    const kitchenSink = {
      set: new Set([1, 3, 3]),
      map: new Map([[1, 2]]),
      regex: /foo/,
      deep: { array: [ new File(someBlobData, 'file.txt') ] },
      error: new Error('Hello!')
    }
    
    const veryProblematicCopy = JSON.parse(JSON.stringify(kitchenSink))

    最终得到如下数据:

    {
      "set": {},
      "map": {},
      "regex": {},
      "deep": {
        "array": [
          {}
        ]
      },
      "error": {},
    }

    2. 使用递归

    代码示例:

    function deepClone(obj) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        let clone = obj.constructor();
        for (let attr in obj) {
            if (obj.hasOwnProperty(attr)) {
                clone[attr] = this.deepClone(obj[attr]);
            }
        }
        return clone;
    }

    优点:对于任何类型的对象都有效,包括循环引用。

    缺点:对于大型对象可能会消耗大量内存,并可能导致堆栈溢出。

    3. 第三方库,如 lodash 的 _.cloneDeep 方法

    代码示例:

    const _ = require('lodash');
    function deepClone(obj) {
        return _.cloneDeep(obj);
    }

    优点:支持更多类型的对象和库,例如,支持 Proxy 对象。

    缺点:会引入依赖导致项目体积增大。

    因为这个函数会导致17.4kb的依赖引入,如果只是引入lodash会更高。

    4. 现代深拷贝structuredClone

    在现代浏览器中,可以使用 structuredClone 方法来实现深拷贝,它是一种更高效、更安全的深拷贝方式。

    以下是一个示例代码,演示如何使用 structuredClone 进行深拷贝:

    const kitchenSink = {
      set: new Set([1, 3, 3]),
      map: new Map([[1, 2]]),
      regex: /foo/,
      deep: { array: [ new File(someBlobData, 'file.txt') ] },
      error: new Error('Hello!')
    }
    kitchenSink.circular = kitchenSink
    
    const clonedSink = structuredClone(kitchenSink)

    structuredClone可以做到:

    • 拷贝无限嵌套的对象和数组
    • 拷贝循环引用
    • 拷贝各种各样的JavaScript类型,如Date、Set、Map、Error、RegExp、ArrayBuffer、Blob、File、ImageData等

    哪些不能拷贝:

    • 函数
    • DOM节点
    • 属性描述、setter和getter
    • 对象原型链

    所支持的完整列表:

    Array、ArrayBuffer、Boolean、DataView、Date、Error类型(下面具体列出的类型)、Map、Object,但仅限于普通对象、原始类型,除了symbol(又名number、string、null、undefined、boolean、BigInt)、RegExp、Set、TypedArray

    Error类型:

    Error, EvalError, RangeError, ReferenceError , SyntaxError, TypeError, URIError

    Web/API类型:

    AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame

    值得庆幸的是 structuredClone 在所有主流浏览器中都受支持,也支持Node.js和Deno。

    最后

    我们现在终于可以直接使用原生JavaScript中的structuredClone能力实现深度拷贝对象。每种方式都有其优缺点,具体使用方式取决于你的需求和目标对象的类型。

    参考

    • Deep Cloning Objects in JavaScript, the Modern Way(www.builder.io/blog/structured-clone)
    • mozilla structuredClone(developer.mozilla.org/zh-CN/docs/Web/API/structuredClone)

    看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

    专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)



沪ICP备19023445号-2号
友情链接