javascript获取参数-JavaScript深拷贝性能分析

2023-08-26 0 6,910 百度已收录

原文:JavaScript 中的深复制 - DasSur.ma

如何在 JavaScript 中复制对象? 对于这个很简单的问题,但答案却并不简单。

通过引用传递

JavaScript 中一切都是按引用传递(原文有误,稍后我会在 JavaScript·Issue #22 中写一篇批判文章《By Value》或《By Reference》)。

如果您不知道这意味着什么,请看下面的示例:

  1. function mutate(obj) {

  2.  obj.a = true;

  3. }

  4. const obj = {a: false};

  5. mutate(obj)

  6. console.log(obj.a); // 输出 true

函数 mutate 改变它的参数。 在按值传递场景中,函数的实参只是形参的副本——副本——并且函数调用完成时左值不会改变。 然而,在像 JavaScript 这样的引用传递场景中,函数的实参和形参指向同一个对象。 当参数内部的数组改变时,函数外部的形参也会改变。

所以在某些情况下,需要保留原始对象,然后需要将原始对象的副本传递给函数以防止函数更改原始对象。

拷贝:Object.assign()

获取对象副本的一种简单方法是使用 Object.assign(target,sources...) 。 它接受任意数量的源对象,枚举它们的所有属性并分配给目标。 如果我们使用一个新的空对象目标,那么我们就可以实现对象复制。

  1. const obj = /* ... */;

  2. const copy = Object.assign({}, obj);

然而这只是一个浅拷贝。 如果我们的对象包含其他对象作为自己的属性,它们将保留共享引用,这不是我们想要的:

  1. function mutateDeepObject(obj) {

  2.  obj.a.thing = true;

  3. }

  4. const obj = {a: {thing: false}};

  5. const copy = Object.assign({}, obj);

  6. mutateDeepObject(copy)

  7. console.log(obj.a.thing); // prints true

Object.assign 方法仅将源对象本身的可枚举属性复制到目标对象。 该方法使用了源对象的[[Get]]和目标对象的[[Set]],因此它调用了相关的getter和setter。 因此,它分配属性,而不仅仅是复制或定义新属性。 如果合并的源包含 getters,这可能会使将新属性合并到原型中变得不合适。 为了将属性定义(包括它们的可枚举性)复制到原型,应使用 Object.getOwnPropertyDescriptor() 和 Object.defineProperty()。

那么现在该怎么办呢? 有多种方法可以创建对象的深层副本。

注:可能有人讲过对象重构操作,这也是浅拷贝。

javascript获取参数-JavaScript深拷贝性能分析

JSON。 解析

创建对象副本的最古老的方法之一是将对象转换为其 JSON 字符串表示形式,然后将其解析回对象。 这感觉有点紧张,但它确实有效:

  1. const obj = /* ... */;

  2. const copy = JSON.parse(JSON.stringify(obj));

这里的缺点是您创建一个临时的、可能很大的字符串,只是为了将其放回解析器中。 另一个缺点是这些技巧无法处理循环对象。 并且循环物体经常发生。 例如,当您构建树形数据结构时,其中节点引用其父节点,而父节点又引用其子节点。

  1. const x = {};

  2. const y = {x};

  3. x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...

  4. const copy = JSON.parse(JSON.stringify(x)); // throws!

此外,外部类型(例如 Map、Set、RegExp、Date、ArrayBuffer 等)在序列化过程中也会丢失。

Structured Clone 结构化克隆算法

javascript获取参数-JavaScript深拷贝性能分析

结构化克隆是一种现有的将值从一个地方转移到另一个地方的算法。 例如,每当您调用 postMessage 将消息发送到另一个窗口或 WebWorker 时,都会使用它。 结构化克隆的好处是它可以处理循环对象并支持大量外部类型。 问题是,在撰写本文时,该算法还不能直接使用,只能作为其他 API 的一部分。 我想我们应该看看其中包含的内容,不是吗? 。 。

消息通道

正如我所说,只要调用 postMessage,结构化克隆算法就可以工作。 我们可以创建一个 MessageChannel 并发送消息。 在接收端,消息包含原始数据对象的结构化克隆。

  1. function structuralClone(obj) {

  2.  return new Promise(resolve => {

  3.    const {port1, port2} = new MessageChannel();

  4.    port2.onmessage = ev => resolve(ev.data);

  5.    port1.postMessage(obj);

  6.  });

  7. }

  8. const obj = /* ... */;

  9. const clone = await structuralClone(obj);

这种方法的缺点是它是异步的。 虽然这没什么大不了的,但有时你需要使用同步方法来深度复制对象。

历史API

如果您在使用history.pushState()之前编写过SPA,您就会知道可以提供一个状态对象来保存URL。 事实证明javascript获取参数,这个状态对象使用了结构化克隆——而且是同步的。 我们必须小心,不要弄乱了程序逻辑所使用的状态对象,因此我们需要在克隆后恢复原始状态。 为了避免任何意外,请使用history.replaceState()而不是history.pushState()。

javascript获取参数-JavaScript深拷贝性能分析

  1. function structuralClone(obj) {

  2.  const oldState = history.state;

  3.  history.replaceState(obj, document.title);

  4.  const copy = history.state;

  5.  history.replaceState(oldState, document.title);

  6.  return copy;

  7. }

  8. const obj = /* ... */;

  9. const clone = structuralClone(obj);

然而,仅仅使用浏览器的引擎来复制对象感觉有点大材小用。 此外,Safari浏览器将30秒内replaceState调用的次数限制为100次。

通知API

在发布推文后,Jeremy Banks 向我展示了利用结构化克隆的第三种方法:通知 API。

  1. function structuralClone(obj) {

  2.  return new Notification('', {data: obj, silent: true}).data;

  3. }

  4. const obj = /* ... */;

  5. const clone = structuralClone(obj);

简短而简洁。 我喜欢!

不过,它需要浏览器内部的权限机制,所以我怀疑它很慢。 由于某种原因,Safari 总是返回未定义。

javascript获取参数-JavaScript深拷贝性能分析

表演盛宴

我想检测哪种方式性能最高。 在我的第一次(天真的)尝试中,我采用了一个小的 JSON 对象,并通过不同的形式克隆了该对象 1000 次。 幸运的是,Mathias Bynens 告诉我,当你向对象添加属性时,V8 有一个缓存。 所以我正在对缓存进行基准测试。 为了确保我永远不会遇到缓存,我编写了一个函数,它使用随机键名称来生成给定深度和长度的对象javascript获取参数,并重新运行测试。

图表!

以下是不同技术在 Chrome、Firefox 和 Edge 中的表现。 越低越好。

综上所述

那么我们能从中得到什么呢?

如果浏览器平台直接提供一个 StructuredClone() 函数不是更好吗? 我其实是这么认为的,最新的HTML规范正在谈论这个: 。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript获取参数-JavaScript深拷贝性能分析 https://www.wkzy.net/game/156026.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务