如何克隆JavaScript ES6类实例


96

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

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

我看到过很多有关对象克隆的讨论,这些讨论表明问题很复杂,但是使用ES6时,它本身就可以提供一个非常简单的解决方案-我将其放在下面,看看人们是否认为它令人满意。

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


2
如果您对Stack Overflow上的旧问题有新的答案,请将该答案添加到原始问题中,而不仅仅是创建一个新的问题。
异端猴子

1
我确实看到了Tom面临的问题,因为ES6类实例的工作方式不同于“常规”对象。
CherryNerd

2
另外,当我尝试在ES6类的实例上运行它时,“可能的重复”所提供的答案中的第一部分代码实际上崩溃了
-CherryNerd

我认为这不是重复的,因为尽管ES6类实例是一个对象,但并不是每个对象都是ES6类实例,因此另一个问题不能解决该问题。
托马什Zato -恢复莫妮卡

5
它不是重复的。另一个问题是关于Object用作数据持有人的pure 。这是关于ES6 classes和不丢失类类型信息的问题。它需要一个不同的解决方案。
flori

Answers:


111

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

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

它避免设置原型,因为他们说这会大大降低代码速度。

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

结论:这是一团糟。希望有一天将提供本机且干净的克隆功能。


1
这不会复制静态方法,因为它们实际上不是可枚举的自身属性。
拉瓦兰普先生17年

5
@ Mr.Lavalamp,如何复制(也)静态方法?
flori

这将破坏阵列!它将使用“ 0”,“ 1”,...键将所有数组转换为对象。
Vahid

1
@KeshaAntonov您也许可以使用typeof和Array方法找到解决方案。我自己更喜欢手动克隆所有属性。
Vahid

1
不要指望它会克隆本身就是对象的属性:jsbin.com/qeziwetexu/edit?js,console
jduhls

10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

注意Object.assign的特征:它执行浅表复制,并且不复制类方法。

如果您想要深层副本或对副本有更多控制,则可以使用lodash克隆功能


2
由于Object.create使用指定的原型创建了新对象,所以为什么不这样做const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah)。同样,类方法也将被复制。
barbatus

1
@barbatus使用错误的原型,虽然,Blah.prototype != instanceOfBlah。您应该使用Object.getPrototypeOf(instanceOfBlah)
Bergi

1
@Bergi不,ES6类实例并不总是具有原型。codepen.io/techniq/pen/qdZeZm,它也适用于实例。
barbatus

@barbatus对不起,什么?我不懂 所有实例都有一个原型,这就是使它们成为实例的原因。试试flori的答案中的代码。
Bergi

1
@Bergi我认为这取决于Babel的配置。我现在正在实现一个反应式本机应用程序,并且没有继承属性的实例的原型为null。您也可以在此处看到developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/…,getPrototypeOf 可能返回null。
barbatus

3

不建议扩展Prototype,这将在您对代码/组件进行测试时导致问题。单元测试框架不会自动采用您的原型扩展。因此,这不是一个好习惯。这里有更多有关原型扩展的解释,为什么扩展本机对象是一种不好的做法?

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

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');
let clone =  Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) //  the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned

结果:

original.logFullName():

结果:卡西欧·塞弗林

clone.logFullName():

结果:约翰·塞弗林

original.address.street;

结果:'Street B,99'//注意原始子对象已更改

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

clone.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->第三方库:

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

下划线:let cloneUnderscore = _(原始).clone();

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

lodash或下划线的缺点是需要在项目中包括一些额外的库。但是,它们是不错的选择,并且还会产生高性能的结果。


1
分配给时{},克隆不会继承原始对象的任何原型方法。clone.logFullName()根本无法工作。在Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)你已经被罚款之前,你为什么要改变这种状况?
Bergi

1
@Bergi感谢您的贡献,我现在正在编辑我的答案,我添加了您的观点来复制原型!
卡西欧

1
感谢@Bergi的帮助,请立即提出您的意见。我已经完成了版本。我认为现在的答案几乎涵盖了所有问题。ks!
卡西欧

1
是的,就像一样Object.assign({},original),它不起作用。
Bergi

1
有时候,我们需要的只是更简单的方法。如果您不需要原型和复杂的对象,只需“克隆= {...原始}”即可解决问题
Cassio Seffrin 19/12/31

0

另一班轮:

大多数时候...(适用于Date,RegExp,Map,String,Number,Array),btw,克隆字符串,number有点有趣。

let clone = new obj.constructor(...[obj].flat())

对于那些没有复制构造函数的类:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

0
class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output = Object.getOwnPropertyNames(Object.getPrototypeOf(a)).concat(Object.getOwnPropertyNames(a)).reduce((accumulator, currentValue, currentIndex, array) => {
  accumulator[currentValue] = a[currentValue];
  return accumulator;
}, {});

在此处输入图片说明


-4

您可以使用传播运算符,例如,如果要克隆名为Obj的对象:

let clone = { ...obj};

如果要更改或向克隆的对象添加任何内容:

let clone = { ...obj, change: "something" };
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.