在 JavaScript 中深度克隆对象的最有效方法是什么?

如何解决在 JavaScript 中深度克隆对象的最有效方法是什么?

Native deep cloning

现在有一个名为structured cloning的 JS 标准,它在 Node 11 及更高版本中进行实验性工作,将登陆浏览器,并且具有适用于现有系统的 polyfill

structuredClone(value)

如果需要,首先加载 polyfill:

import structuredClone from '@ungap/structured-clone';

较旧的答案

数据丢失的快速克隆 - JSON.parse/stringify

如果不使用DateS,功能,undefinedInfinity,正则表达式,地图,集合,斑点,的文件列表,ImageDatas,稀疏数组,类型化数组或其他复杂类型的对象中,一个很简单的一个衬垫深克隆的对象是:

JSON.parse(JSON.stringify(object))
const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

使用library进行可靠的克隆

由于克隆对象并非易事(复杂类型、循环引用、函数等),大多数主要库都提供了克隆对象的函数。- 如果您已经在使用库,请检查它是否具有对象克隆功能。例如,

ES6(shallow copy)

为了完整起见,请注意 ES6 提供了两种浅拷贝机制:Object.assign()spread syntax. 。它将所有可枚举属性的值从一个对象复制到另一个对象。例如:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

结构化克隆

structuredClone全局函数即将登陆浏览器,Node.js的,和杰诺。

HTML 标准包括,可以创建对象的深层克隆。它仍然仅限于某些内置类型,但除了 JSON 支持的少数类型之外,它还支持日期、正则表达式、地图、集合、Blob、文件列表、图像数据、稀疏数组、类型化数组,以及将来可能更多. 它还保留克隆数据中的引用,使其支持可能导致 JSON 错误的循环和递归结构。

Node.js 中的支持:实验性

structuredClone全球功能将很快被Node.js的提供:

const clone = structuredClone(original);

在那之前:v8Node.js 中的模块当前(从 Node 11 开始)直接公开结构化序列化 API,但此功能仍标记为“实验性”,并且在未来版本中可能会更改或删除。如果您使用的是兼容版本,则克隆对象非常简单:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

浏览器的直接支持:即将推出

structuredClone全局函数很快就会被所有主流浏览器提供(前面已经讨论WHATWG / HTML#793 GitHub上)。它看起来/看起来像这样:

const clone = structuredClone(original);

在发布之前,浏览器的结构化克隆实现只是间接公开。

异步解决方法:可用。

使用现有 API 创建结构化克隆的开销较低的方法是通过MessageChannels 的一个端口发布数据。另一个端口将发出一个message带有附加.data. 不幸的是,监听这些事件必然是异步的,而同步的替代方案不太实用。

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

使用示例:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

同步解决方法:太糟糕了!

同步创建结构化克隆没有好的选择。这里有一些不切实际的技巧。

history.pushState()并且history.replaceState()都创建了第一个参数的结构化克隆,并将该值分配给history.state. 您可以使用它来创建任何对象的结构化克隆,如下所示:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

使用示例:

Hide code snippet

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

虽然是同步的,但这可能会非常慢。它会产生与操纵浏览器历史记录相关的所有开销。重复调用此方法可能会导致 Chrome 暂时无响应。

Notification构造函数创建其相关数据的结构化克隆。它还尝试向用户显示浏览器通知,但除非您请求通知权限,否则这将静默失败。如果您有其他用途的许可,我们将立即关闭我们创建的通知。

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

使用示例:

Hide code snippet

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();

解决方法

我做过类似obj = JSON.parse(JSON.stringify(o));但质疑效率的事情。

我还看到了具有各种缺陷的递归复制函数。
我很惊讶不存在规范的解决方案。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?