如何克隆一个 javascript ES6 类实例

IT技术 javascript node.js ecmascript-6 es6-class
2021-03-12 02:05:16

如何使用 ES6 克隆 Javascript 类实例。

我对基于 jquery 或 $extend 的解决方案不感兴趣。

我已经看到关于对象克隆的相当古老的讨论,这些讨论表明问题非常复杂,但是使用 ES6 提供了一个非常简单的解决方案 - 我将把它放在下面,看看人们是否认为它令人满意。

编辑:有人建议我的问题是重复的;我看到了那个答案,但它已经有 7 年的历史了,并且涉及使用 ES6 之前的 js 的非常复杂的答案。我建议我的问题(允许使用 ES6)有一个非常简单的解决方案。

6个回答

这很复杂; 我试了很多!最后,这个单行代码适用于我的自定义 ES6 类实例:

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

它避免设置原型,因为他们说它会大大减慢代码的速度。

它支持符号,但对于 getter/setter 并不完美,并且不适用于不可枚举的属性(请参阅Object.assign() 文档)。此外,可悲的是,克隆基本的内部类(如 Array、Date、RegExp、Map 等)似乎经常需要一些单独的处理。

结论:一团糟。让我们希望有一天会有一个原生的和干净的克隆功能。

@KeshaAntonov 您也许可以找到带有 typeof 和 A​​rray 方法的解决方案。我自己更喜欢手动克隆所有属性。
2021-04-21 02:05:16
这将破坏阵列!它会将所有数组转换为具有“0”、“1”、...键的对象。
2021-04-27 02:05:16
@Mr.Lavalamp 以及如何复制(也)静态方法?
2021-04-28 02:05:16
不要指望它克隆本身就是对象的属性:jsbin.com/qeziwetexu/edit?js,console
2021-05-08 02:05:16
这不会复制静态方法,因为它们实际上不是可枚举的自己的属性。
2021-05-09 02:05:16
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

注意Object.assign的特点:它做一个浅拷贝,不拷贝类方法。

如果你想要一个深拷贝或对拷贝的更多控制,那么有lodash clone 函数

@barbatus 虽然使用了错误的原型,但是Blah.prototype != instanceOfBlah你应该使用Object.getPrototypeOf(instanceOfBlah)
2021-04-19 02:05:16
@barbatus 对不起,什么?我不跟。所有实例都有一个原型,这就是使它们成为实例的原因。试试弗洛里回答中的代码。
2021-04-20 02:05:16
@Bergi 我认为这取决于 Babel 配置或其他什么。我现在正在实现一个反应式本机应用程序,并且没有继承属性的实例在那里具有原型 null。同样,正如您在此处看到的developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... getPrototypeOf 可能返回 null。
2021-04-20 02:05:16
@Bergi 不,ES6 类实例并不总是有原型。查看codepen.io/techniq/pen/qdZeZm它也适用于该实例。
2021-04-28 02:05:16
既然Object.create创建了具有指定原型的新对象,那么为什么不只是const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). 类方法也将被复制。
2021-05-15 02:05:16

我喜欢几乎所有的答案。我遇到了这个问题,为了解决它,我会通过定义一个clone()方法来手动完成它,并在其中从头开始构建整个对象。对我来说,这是有道理的,因为结果对象自然与克隆对象具有相同的类型。

typescript示例:

export default class ClassName {
    private name: string;
    private anotherVariable: string;
   
    constructor(name: string, anotherVariable: string) {
        this.name = name;
        this.anotherVariable = anotherVariable;
    }

    public clone(): ClassName {
        return new ClassName(this.name, this.anotherVariable);
    }
}

我喜欢这个解决方案,因为它看起来更“面向对象”

这确实是前进的方向。这是非常难以得到克隆机制的作品一般。不可能让它在每一个案例中都能正常工作。总会有奇怪和不一致的类。因此,确保您的对象本身是可克隆的是唯一确定的方法。作为替代(或添加),可以有一种方法实例进行克隆,类似于public static clone(instance: MyClass): MyClass)处理克隆的相同想法,只是将其置于实例外部。
2021-04-25 02:05:16

TLDR;

// Use this approach
//Method 1 - clone will inherit the prototype methods of the original.
    let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

在 Javascript 中,不建议对 Prototype 进行扩展,这会导致在对代码/组件进行测试时出现问题。单元测试框架不会自动假设您的原型扩展。所以这不是一个好习惯。这里有更多关于原型扩展的解释为什么扩展原生对象是一种不好的做法?

要在 JavaScript 中克隆对象,没有一种简单或直接的方法。这是使用“浅拷贝”的第一个实例:

1 -> 浅克隆:

class Employee {
    constructor(first, last, street) {
        this.firstName = first;
        this.lastName = last;
        this.address = { street: street };
    }

    logFullName() {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');

//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

//Method 2 - object.assing() will not clone the Prototype.
let cloneWithoutPrototype =  Object.assign({},original); 

//Method 3 - the same of object assign but shorter syntax using "spread operator"
let clone3 = { ...original }; 

//tests
cloneWithoutPrototype.firstName = 'John';
cloneWithoutPrototype.address.street = 'Street B, 99'; //will not be cloned

结果:

original.logFullName();

结果:卡西奥·塞弗林

cloneWithPrototype.logFullName();

结果:卡西奥·塞弗林

original.address.street;

result: 'Street B, 99' // 注意原来的子对象被改变了

注意:如果实例具有闭包作为自己的属性,则此方法将不会包装它。阅读更多关于闭包的信息)此外,子对象“地址”不会被克隆。

cloneWithoutPrototype.logFullName()

不管用。克隆不会继承原始的任何原型方法。

cloneWithPrototype.logFullName()

会起作用,因为克隆也会复制它的原型。

使用 Object.assign 克隆数组:

let cloneArr = array.map((a) => Object.assign({}, a));

使用 ECMAScript 传播语法克隆数组:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> 深度克隆:

要归档一个全新的对象引用,我们可以使用 JSON.stringify() 将原始对象解析为字符串,然后将其解析回 JSON.parse()。

let deepClone = JSON.parse(JSON.stringify(original));

使用深度克隆,将保留对地址的引用。然而,deepClone 原型将会丢失,因此 deepClone.logFullName() 将不起作用。

3 -> 第 3 方库:

另一种选择是使用第 3 方库,如 loadash 或下划线。他们将创建一个新对象并将每个值从原始对象复制到新对象,并将其引用保留在内存中。

下划线:让 cloneUnderscore = _(original).clone();

Loadash 克隆:var cloneLodash = _.cloneDeep(original);

lodash 或下划线的缺点是需要在项目中包含一些额外的库。然而,它们是不错的选择,也能产生高性能的结果。

分配给 时{},克隆不会继承原始的任何原型方法。clone.logFullName()根本行不通。Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)你已经被罚款之前,你为什么要改变这种状况?
2021-04-20 02:05:16
有时,我们只需要更简单的方法。如果你不需要原型和复杂的对象可能只需要“clone = { ...original }”就可以解决问题
2021-04-21 02:05:16
感谢您的帮助@Bergi,现在请发表您的意见。我已经完成了版本。我想现在答案几乎涵盖了所有问题。谢谢!
2021-04-25 02:05:16
是的,就像 一样Object.assign({},original),它不起作用。
2021-04-26 02:05:16
@Bergi 感谢您的贡献,我现在正在编辑我的答案,我添加了您的观点来复制原型!
2021-04-27 02:05:16

使用与原始对象相同的原型和相同的属性创建对象的副本。

function clone(obj) {
  return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}

适用于不可枚举的属性、getter、setter 等。无法克隆许多内置 javascript 类型(例如数组、映射、代理)所具有的内部插槽

这是一个很好的方法,因为它将所有这些所需的大量处理委托给 JavaScript。但是,它对任何潜在的对象值都有问题,因为它们将在原始对象和克隆对象之间共享。例如,一个数组值将被两个实例更新。
2021-05-02 02:05:16