如何克隆 javascript ES6 类实例

Posted

技术标签:

【中文标题】如何克隆 javascript ES6 类实例【英文标题】:How to clone a javascript ES6 class instance 【发布时间】:2017-05-19 09:40:36 【问题描述】:

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

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

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

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

【问题讨论】:

如果您对 Stack Overflow 上的旧问题有新的答案,请将该答案添加到原始问题中,不要只是创建一个新问题。 我确实看到了 Tom 面临的问题,因为 ES6 类实例的工作方式与“常规”对象不同。 另外,当我尝试在 ES6 类的实例上运行时,您的“可能重复”提供的已接受答案中的第一段代码实际上会崩溃 我认为这不是重复的,因为虽然 ES6 类实例是一个对象,但并非每个对象都是 ES6 类实例,因此另一个问题没有解决这个问题的问题。 不是重复的。另一个问题是关于纯Objects 用作数据持有者。这个是关于 ES6 classes 和不丢失类类型信息的问题。它需要一个不同的解决方案。 【参考方案1】:

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

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

它避免设置原型,因为they say 它会大大减慢代码速度。

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

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

【讨论】:

这不会复制静态方法,因为它们实际上不是可枚举的自己的属性。 @Mr.Lavalamp 以及如何复制(也)静态方法? 这会破坏数组!它将所有数组转换为具有 "0","1",... 键的对象。 @KeshaAntonov 您也许可以使用 typeof 和 A​​rray 方法找到解决方案。我自己更喜欢手动克隆所有属性。 不要期望它克隆本身就是对象的属性:jsbin.com/qeziwetexu/edit?js,console【参考方案2】:
const clone = Object.assign( , instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

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

如果您想要深拷贝或对拷贝进行更多控制,那么可以使用 lodash clone functions。

【讨论】:

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

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

打字稿示例:

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),它具有相同的想法,即专门处理克隆只是使其位于实例外部。 【参考方案4】:

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 中,不建议对原型进行扩展,当您对代码/组件进行测试时会导致问题。单元测试框架不会自动假设您的原型扩展。所以这不是一个好习惯。 这里有更多关于原型扩展的解释Why is extending native objects a bad practice?

在 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();

结果:Cassio Seffrin

cloneWithPrototype.logFullName();

结果:Cassio Seffrin

original.address.street;

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

注意:如果实例有闭包作为自己的属性,则此方法不会包装它。 (read more about closures) 而且,子对象“地址”不会被克隆。

cloneWithoutPrototype.logFullName()

不会工作。克隆不会继承原始的任何原型方法。

cloneWithPrototype.logFullName()

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

使用 Object.assign 克隆数组:

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

使用 ECMAScript spread sintax 克隆数组:

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

2 -> 深度克隆:

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

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

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

3 -> 第三方库:

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

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

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

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

【讨论】:

当分配给时,克隆不会继承原始的任何原型方法。 clone.logFullName() 根本不起作用。你之前的Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal) 没问题,为什么改了? @Bergi 感谢您的贡献,我现在正在编辑我的答案,我添加了您的点来复制原型! 感谢您的帮助@Bergi,现在请发表您的意见。我已经完成了版本。我想现在答案几乎涵盖了所有问题。谢谢! 是的,就像Object.assign(,original)一样,它不起作用。 有时我们只需要更简单的方法。如果您不需要原型和复杂对象,可能只需“克隆 = ...original ”即可解决问题【参考方案5】:

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

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

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

【讨论】:

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

试试这个:

function copy(obj) 
   //Edge case
   if(obj == null || typeof obj !== "object")  return obj; 

   var result = ;
   var keys_ = Object.getOwnPropertyNames(obj);

   for(var i = 0; i < keys_.length; i++) 
       var key = keys_[i], value = copy(obj[key]);
       result[key] = value;
   

   Object.setPrototypeOf(result, obj.__proto__);

   return result;


//test
class Point 
    constructor(x, y) 
        this.x = x;
        this.y = y;
    
;

var myPoint = new Point(0, 1);

var copiedPoint = copy(myPoint);

console.log(
   copiedPoint,
   copiedPoint instanceof Point,
   copiedPoint === myPoint
);
由于它使用Object.getOwnPropertyNames,它还会添加不可枚举的属性。

【讨论】:

【参考方案7】:

如果我们有多个相互扩展的类,克隆每个实例的最佳解决方案是定义一个函数来在其类定义中创建该对象的新实例,例如:

class Point 
  constructor(x, y) 
    this.x = x;
    this.y = y;
  
  clone() 
    return new Point(this.x, this.y);
  

class ColorPoint extends Point 
  constructor(x, y, color) 
    super(x, y);
    this.color = color;
  
  clone() 
    return new ColorPoint(
      this.x, this.y, this.color.clone()); // (A)
  

现在我可以使用 clone(0 对象的函数了:

let p = new ColorPoint(10,10,'red');
let pclone=p.clone();

【讨论】:

【参考方案8】:

另一个班轮:

大多数时候...(适用于日期、正则表达式、映射、字符串、数字、数组),顺便说一句,克隆字符串,数字有点好笑。

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

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

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

【讨论】:

fn(...[obj].flat()) === fn(obj) 没有真正的理由需要额外的 1. 数组,2. 扁平化为具有单个成员的数组。 3. 将单个成员传播到一个参数中。即便如此,这只适用于具有复制构造函数的类型。第二个版本不一定适用于没有复制构造函数的类 - 它甚至可能导致错误,请考虑 constructor(a, b) this.c = a + b 它通常需要数字但为 aundefined 获取自身的实例 @987654328 @.【参考方案9】:

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;
  , );
  
console.log(output);

【讨论】:

这里有两个问题 - 1. 这会丢失类信息 - output instanceof Afalse。 2. 克隆只是原型链的上一级,如果有class B extends A b() return 2; class C extends B c() return 3; ,那么“克隆”C 的实例最终只会复制b()c() 的属性,但不会复制A (y)。 x 属性将被复制,因为它直接在实例上的构造函数中设置。【参考方案10】:

这样还不够吗?

Object.assign(new ClassName(), obj)

【讨论】:

取决于班级。如果它很简单,这可能就足够了。但是关于构造函数中的代码呢?当您克隆此对象时,它会做什么以及您希望它运行吗?闭包呢,比如箭头函数?这些你不能复制或this 会指向旧实例,然后有私有字段,......很多陷阱【参考方案11】:

您可以使用扩展运算符,例如,如果您想克隆一个名为 Obj 的对象:

let clone =  ...obj;

如果你想更改或添加任何东西到克隆对象:

let clone =  ...obj, change: "something" ;

【讨论】:

这会丢失包含类的 tprototype 信息。如果obj = new A(),那么clone instanceof A 就是false。这也意味着方法会丢失,实例可能具有的任何其他不可枚举属性也会丢失。

以上是关于如何克隆 javascript ES6 类实例的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript克隆“类”实例[重复]

JavaScript克隆“类”实例[重复]

如何向 ES6 javascript 类添加 mixins?

对Javascript 类原型链继承的理解

JavaScript进阶面向对象ES6

构造函数、ECMAscript(ES6)