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

这篇文章发布于 2025年01月22日,星期三,23:22,归类于 JS API。 阅读 1514 次, 今日 39 次 没有评论

 

深拷贝示意图

一、开门即见山

目前,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并不简单书封

(本篇完)

分享到:


发表评论(目前没有评论)