您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

深入理解JavaScript深拷贝性能

2026/2/5 22:14:26发布10次查看
本文主要和大家分享javascript深拷贝性能的分析,如何在 javascript 中拷贝一个对象?对于这个很简单的问题,但是答案却不简单。
如果你不知道什么意思,看看下面的例子:
function mutate(obj) {   obj.a = true; } const obj = {a: false}; mutate(obj) console.log(obj.a); // 输出 true
函数 mutate 改变了它的参数。在值传递的场景中,函数的形参只是实参的一个副本——a copy——当函数调用完成后,并不改变实参。但是在 javascript 这种引用传递的场景中,函数的形参和实参指向同一个对象,当参数内部改变形参的时候,函数外面的实参也被改变了。
因此在某些情况下,你需要保留原始对象,这时你需要把原始对象的一个拷贝传入到函数中,以防止函数改变原始对象。
浅拷贝:object.assign()一个简单的获取对象拷贝的方式是使用 object.assign(target, sources...)。它接受任意数量的源对象,枚举它们的所有属性并分配给target。如果我们使用一个新的空对象target,那么我们就可以实现对象的复制。
const obj = /* ... */; const copy = object.assign({}, obj);
然而这只是一个浅副本。如果我们的对象包含其它对象作为自己的属性,它们将保持共享引用,这不是我们想要的:
function mutatedeepobject(obj) {   obj.a.thing = true; } const obj = {a: {thing: false}}; const copy = object.assign({}, obj); mutatedeepobject(copy) console.log(obj.a.thing); // prints true
object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[get]]和目标对象的[[set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用object.getownpropertydescriptor()和object.defineproperty() 。所以现在怎么办?有几种方法可以创建一个对象的深拷贝。
注意:也许有人提到了对象解构运算,这也是浅拷贝。
json.parse创建对象副本的最古老方法之一是:将该对象转换为其 json 字符串表示形式,然后将其解析回对象。这感觉有点压抑,但它确实有效:
const obj = /* ... */; const copy = json.parse(json.stringify(obj));
这里的缺点是你创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。另一个缺点是这种方法不能处理循环对象。而且循环对象经常发生。例如,当您构建树状数据结构,其中一个节点引用其父级,而父级又引用其子级。
const x = {}; const y = {x}; x.y = y; // cycle: x.y.x.y.x.y.x.y.x... const copy = json.parse(json.stringify(x)); // throws!
另外,诸如 map, set, regexp, date, arraybuffer 和其他内置类型在进行序列化时会丢失。
structured clone 结构化克隆算法structured cloning 是一种现有的算法,用于将值从一个地方转移到另一地方。例如,每当您调用postmessage将消息发送到另一个窗口或 webworker 时,都会使用它。关于结构化克隆的好处在于它处理循环对象并 支持大量的内置类型。问题是,在编写本文时,该算法并不能直接使用,只能作为其他 api 的一部分。我想我们应该了解一下包含哪些,不是吗。。。
messagechannel正如我所说的,只要你调用postmessage结构化克隆算法就可以使用。我们可以创建一个 messagechannel 并发送消息。在接收端,消息包含我们原始数据对象的结构化克隆。
function structuralclone(obj) {   return new promise(resolve => {     const {port1, port2} = new messagechannel();     port2.onmessage = ev => resolve(ev.data);     port1.postmessage(obj);   }); } const obj = /* ... */; const clone = await structuralclone(obj);
这种方法的缺点是它是异步的。虽然这并无大碍,但是有时候你需要使用同步的方式来深度拷贝一个对象。
history api如果你曾经使用history.pushstate()写过 spa,你就知道你可以提供一个状态对象来保存 url。事实证明,这个状态对象使用结构化克隆 - 而且是同步的。我们必须小心使用,不要把程序逻辑使用的状态对象搞乱了,所以我们需要在完成克隆之后恢复原始状态。为了防止发生任何意外,请使用history.replacestate()而不是history.pushstate()。
function structuralclone(obj) {   const oldstate = history.state;   history.replacestate(obj, document.title);   const copy = history.state;   history.replacestate(oldstate, document.title);   return copy; } const obj = /* ... */; const clone = structuralclone(obj);
然而,仅仅为了复制一个对象,而使用浏览器的引擎,感觉有点过分。另外,safari 浏览器对replacestate调用的限制数量为 30 秒内 100 次。
notification api在发了一条推文之后,jeremy banks 向我展示了第三种方法来利用结构化克隆:notification api。
function structuralclone(obj) {   return new notification('', {data: obj, silent: true}).data; } const obj = /* ... */; const clone = structuralclone(obj);
短小,简洁。我喜欢它!
但是,它需要浏览器内部的权限机制,所以我怀疑它是很慢的。由于某种原因,safari 总是返回undefined。
performance extravaganza我想测量哪种方法是最高性能的。在我的第一次(天真的)尝试中,我拿了一个小 json 对象,并通过不同的方式克隆对象 1 千次。幸运的是,mathias bynens 告诉我,当你添加属性到一个对象时,v8有一个缓存。所以我是在给缓存做基准测试。为了确保我永远不会碰到缓存,我编写了一个函数,使用随机密钥名称生成给定深度和宽度的对象,并重新运行测试。
图表!以下是 chrome,firefox 和 edge 中不同技术的性能。越低越好。
结论那么我们从中得到了什么呢?
如果您没有循环对象,并且不需要保留内置类型,则可以使用跨浏览器的json.parse(json.stringify())获得最快的克隆性能,这让我感到非常惊讶。
如果你想要一个适当的结构化克隆,messagechannel是你唯一可靠的跨浏览器的选择。
如果浏览器平台直接提供一个 structuredclone()函数,会不会更好?我当然这样认为,最新的 html 规范正在讨论这个 synchronous clone = global.structuredclone(value, transfer = []) api · issue #793 · whatwg/html。
相关推荐:
jquery的$.extend浅拷贝与深拷贝实例解析
在jquery中实现深拷贝与浅拷贝
什么是js中的浅、深拷贝
以上就是深入理解javascript深拷贝性能的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product