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

    JS原生的深拷贝API structuredClone函数简介

    张 鑫旭发表于 2025-01-22 15:22:42
    love 0

    by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11509
    本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。

    深拷贝示意图

    一、开门即见山

    目前,Web浏览器提供了原生的Object对象深度克隆方法structuredClone()函数。

    使用方法很简单,JS代码如下所示:

    // 创建一个具有值和循环引用的对象
    const original = { name: "zhangxinxu" };
    original.itself = original;
    
    // 克隆
    const clone = structuredClone(original);
    
    // 两者对象是不相等的
    console.assert(clone !== original);
    // 两者的值是相等的
    console.assert(clone.name === "zhangxinxu"); 
    // 并且保留了循环引用
    console.assert(clone.itself === clone);

    语法

    语法如下:

    structuredClone(value, options)

    其中:

    value
    需要被深拷贝的值
    options
    可选参数,支持一个名为transfer的参数值,其值为一组可转移的对象,它们将被移动而不是克隆到返回的对象中。

    关于transfer

    可选参数transfer多用在一些大数据传输中(转移相比克隆可以节约内存开销),这里有个案例供大家参考:

    const original = new Uint8Array(1024);
    const clone = structuredClone(original);
    console.log(original.byteLength); // 1024
    console.log(clone.byteLength); // 1024
    
    original[0] = 1;
    console.log(clone[0]); // 0
    
    // 转移Uint8Array会引发异常,因为它不是可转移对象
    // const transferred = structuredClone(original, {transfer: [original]});
    
    // 我们可以转移Uint8Array.buffer
    const transferred = structuredClone(original, { transfer: [original.buffer] });
    console.log(transferred.byteLength); // 1024
    console.log(transferred[0]); // 1
    
    // Uint8Array.buffer转移后就无法使用了
    console.log(original.byteLength); // 0

    二、兼容性与Polyfill方法

    window全局的structuredClone()方法兼容性还是不错的,目前所有常见浏览器都已经支持了,如下截图所示:

    structuredClone兼容性截图

    考虑到总会有一些用户手机舍不得或者忘记或者懒得升级,面对偏外部用户的产品,建议还是同时引入Polyfill。

    structuredClone Polyfill

    JS不同于CSS,要是JS某个方法不支持,然后你去运行他,很可能会导致整个页面白屏,这是Vue和React项目中是常有的事情(也包括各类小程序)。

    所以,我们需要引入Polyfill,可以试试这个项目:https://github.com/ungap/structured-clone

    使用示意:

    import structuredClone from '@ungap/structured-clone';
    const cloned = structuredClone({any: 'serializable'});

    还是很easy的啦。

    其实,上面的Polyfill还有不少其他的功能,就等大家自行去探索啦。

    三、JSON等方法有什么问题

    之前我深度拷贝一个Object对象会使用JSON.parse(JSON.stringify(obj))来实现,虽然可以满足绝大多数的场景,但有时候会出问题。

    例如,当对象的属性值是Date()对象的时候,案例示意:

    const originObj = {
      name: "zhangxinxu",
      date: new Date()
    };
    
    const cloneObj = JSON.parse(JSON.stringify(originObj));
    
    // 结果是 'object'
    console.log(typeof originObj.date);
    // 结果是 'string'
    console.log(typeof cloneObj.date);

    可以看到,本应实时显示当下时间的属性值变成了固定死的字符串值(也可以看截图运行结果),这并不是我们希望看到的。

    JSON方法变成字符串示意

    而浏览器提供的structuredClone()方法则没有这个问题,使用示意:

    const originObj = {
      name: "zhangxinxu",
      date: new Date()
    };
    
    const cloneObj = structuredClone(originObj);
    
    // 结果是 'object'
    console.log(typeof originObj.date);
    // 结果是 'object'
    console.log(typeof cloneObj.date);

    Date复制示意

    当然,还包括很多其他类型的对象也是如此,包括:Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData等。

    点点点或者Object方法的问题

    如果需要复制的对象层级简单,那么我们使用点点点,或者Object.assign()、Object.create()方法是没问题的,例如:

    const originObj = {
      name: "zhangxinxu"
    };
    // ok没问题
    const cloneObj = { ... originObj }
    // ok没问题
    const cloneObj = Object.assign({}, originObj)
    // ok没问题
    const cloneObj = Object.create(originObj)

    可如果对象的属性值也是个对象,那么上面的方法就有问题,例如:

    const originObj = {
      name: "zhangxinxu",
      books: ['CSS世界']
    };
    // ok没问题
    const cloneObj = { ... originObj }
    cloneObj.books.push('HTML并不简单');
    // 结果原对象的books也一起变化了
    console.log(originObj.books);

    控制台运行结果不会骗人:

    嵌套结构有问题示意

    四、structuredClone不能的局限

    当然,structuredClone方法也不是万能的,例如DOM对象是不能参与复制的。

    // 会报错
    structuredClone({ el: document.body })

    DOM对象不能复制

    函数也不能复制:

    // 会报错
    structuredClone({ fn: () => { } })

    属性描述符、setter和getter

    标题这些类型的东西也不会被深度复制,比方说像getter,克隆的会是其值,而不是getter函数本身。

    structuredClone({ get foo() { return 'bar' } })
    // 结果: { foo: 'bar' }

    对象原型

    原型链也是不会被复制的,因此,如果克隆MyClass的实例,克隆的对象将不再是该类的实例(但该类的所有有效属性都将被克隆)

    class MyClass { 
      foo = 'bar' 
      myMethod() { /* ... */ }
    }
    const myClass = new MyClass()
    
    const cloned = structuredClone(myClass)
    // 结果 { foo: 'bar' }
    
    cloned instanceof myClass // false

    五、蛇年快乐

    好,本文的内容就这些,应该是春节前的最后一篇文章了,本来以为内容不多,但写着写着,发现里面可讲的东西还不少。

    我明天就请假回老家了,算算,可以连休12天,还真是富裕的假期。

    就是过年很多鱼塘不开门,想要出去钓鱼,还有些困难。

    唉,再说吧。

    话不多说,祝大家蛇年快乐,万事如意。

    在家要是无聊,可以看看技术书籍

    HTML并不简单书封

    本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
    本文地址:https://www.zhangxinxu.com/wordpress/?p=11509

    (本篇完)



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