在JavaScript中如何深度克隆一个类的实例

12

我正在尝试找到一种方法来深度克隆一个JS类实例,同时保留整条原型链。

我已经看到了如何深度克隆一个对象:

JSON.parse(JSON.stringify(instance))

我已经学会如何制作一个类实例的浅拷贝:

Object.assign( Object.create( Object.getPrototypeOf(instance) ), instance)

但我的问题是,有没有办法深度克隆一个类的实例?


1
你是想克隆一个类本身还是一个类的实例?如果你想克隆一个类,请解释一下原因,你可能只需要继承它。 - jfriend00
我正在尝试克隆一个实例,我会编辑我的问题以使其更清晰。 - Mendy
4
在JS中,没有一种百分百可靠的方法来克隆所有可能类型的对象,特别是当它包含对其他对象的引用时。最好的方式是在对象本身上支持一个.clone()方法(或者使用其他名称),并使对象支持制作自身的副本。然后,它可以对任何实例数据进行正确处理,而只有对象实现本身才知道如何处理所有可能类型的实例数据。 - jfriend00
3
不应该依赖于JSON.parse和JSON.stringify来克隆非简单数据结构的对象(即使对于简单数据结构,它也很)。JSON处理程序会计算获取器的一次性值而不是克隆定义,并忽略不可枚举的属性和符号、原型(正如您已经注意到的那样),并且根本不尝试处理循环引用。可以通过JSON.parse的可选参数及其相应的JSON.stringify参数来改进这种情况。 - Touffy
3个回答

12
在JS中,没有一种万无一失的方法可以克隆所有可能类型的对象,特别是当它包含对其他对象的引用时。一个通用的克隆参数并不知道克隆中的对象引用是否应该包含相同的引用(例如共同的父对象),或者它是否需要克隆我也有一个引用的对象。这在通用情况下是不可能知道的,因为它实际上取决于对象的实现。
如果存在循环引用到对象,例如从父对象到子对象和从子对象到父对象,那么情况就更加复杂了。
举个例子,想象一个对象,作为其构造函数的一部分,它创建了一个唯一的对象ID,将该ID注册到某个服务中,然后将ID存储在其实例数据中。通用的克隆机制无法知道那些逻辑(生成新ID并将其注册到一些服务)是必要的来创建一个新对象。这种类型的逻辑必须由特定于该对象的代码完成,它们知道该做什么。
另外,构造函数可能会创建闭包(访问私有信息),而外部不存在任何复制闭包的方法。
再例如,构造函数可能会将方法绑定到自己的实例上,而通用的克隆则不知道它需要执行此操作。

最好的克隆对象的方法是使用内置于对象实现中的代码来克隆自身,例如在对象本身上添加一个.clone()方法(或者叫任何你想要的名字),并使该对象支持制作自己的副本。然后,它可以针对任何实例数据执行正确的操作,这些数据只有对象实现本身才知道如何处理所有可能类型的实例数据。


1
与注册示例类似,构造函数可能会创建闭包或绑定方法,并且无法从外部访问/复制它们的状态。 - Bergi
@Bergi - 我添加了这些示例。 - jfriend00

7
我建议使用Lodash的cloneDeep。它适用于所有类型,函数和符号通过引用复制。
当存在循环引用时,使用JSON.parse(JSON.stringify(myObject))方式存在问题。此外,它会将对象的方法替换为undefined并重新排序属性。

这里的答案很简单,无需重复发明轮子,只需使用Lodash的cloneDeep函数,效果非常好。 背景也很简单,如果你想要一个真正新的对象,你需要遍历对象的所有键并将其复制到新对象中,同时还要复制值,以防漏掉任何东西。 - xzesstence

4
一种可能的黑客方式是通过使用MessageChannel发送实例并调用结构化克隆算法来进行克隆,示例如下:

结构化克隆算法

function deepClone(instance) {
    return new Promise(resolve => {
        const messageChannel = new MessageChannel();
        messageChannel.port2.onmessage = e => resolve(e.data);
        messageChannel.port1.postMessage(instance);
    });
}

这将处理像Map、Set、Date、RegExp、Blob、ArrayBuffer和更多这样的类型,甚至可以克隆自定义类。
您可以在此处找到触发结构化克隆的其他方法。

1
我喜欢使用结构化克隆的想法。一个不那么hacky且实际上快速的解决方案就是直接实现它。它已经被很好地规定了,所以应该不会有太多麻烦。 - Touffy
1
@Touffy 你知道有没有JavaScript的实现吗?这听起来是个好主意。 - Mendy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接