如何克隆JavaScript类实例?

时间:2022-10-30 15:08:40

How do I clone a JavaScript class instance?

如何克隆JavaScript类实例?

I tried the normal jQuery extend, but that just returns a vanilla object. I have looked through many other answers on stack, but could not find how to clone an instance.

我尝试了普通的jQuery扩展,但这只是返回一个vanilla对象。我在堆栈上查看了许多其他答案,但找不到如何克隆实例。

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function() {
    console.log('Hello my name is ' + this.name);
}

function Child(name) {
    Parent.call(this, name);
}

Child.prototype = Object.create(Parent.prototype);

var child = new Child('Billy');

var clone = $.extend(true, {}, child);
clone.name = 'Bob';

child.sayHello();
clone.sayHello();

console.log(child instanceof Child);
console.log(clone instanceof Child);

http://jsfiddle.net/39gjA/

I would prefer that the clone was deep/recursive. I.E. all properties that are objects are cloned as well.

我希望克隆是深度/递归的。 I.E.作为对象的所有属性也被克隆。

6 个解决方案

#1


11  

How do I clone a JavaScript class instance?

如何克隆JavaScript类实例?

It's hardly possible if the instance was created with heavy use of closures in the constructor function. We may never now which internal values were set, and how to reproduce such a setup. Therefore, the easiest way would be if every class offered a clone function which knows what to do.

如果在构造函数中大量使用闭包创建实例,则几乎不可能。我们现在可能永远不会设置哪些内部值,以及如何重现这样的设置。因此,最简单的方法是每个类都提供克隆功能,知道该怎么做。

normal jQuery extend just returns a vanilla object

普通的jQuery扩展只返回一个vanilla对象

Not necessarily, it returns what you passed in. Use

不一定,它会返回您传入的内容。使用

var clone = $.extend(true, Object.create(Object.getPrototypeOf(child)), child);

instead and your instanceof usage will work fine. Note that the true signifies a "deep" copy which may or may not be what you want. Also, $.extend will happily copy enumerable inherited properties as well, so you might need to use a more sophisticated extend function.

相反,您的instanceof用法将正常工作。请注意,true表示“深层”副本,可能是您想要的,也可能不是。此外,$ .extend也会愉快地复制可枚举的继承属性,因此您可能需要使用更复杂的扩展函数。

Or without jQuery at all, and only copying own, enumerable properties and only using a shallow copy:

或者根本没有jQuery,只复制自己的,可枚举的属性,只使用浅拷贝:

var clone = Object.assign(Object.create(Object.getPrototypeOf(child)), child);

But again, not all objects will be clonable in this way, see my first point above.

但同样,并非所有对象都会以这种方式克隆,请参阅上面的第一点。

#2


3  

This will create a copy of an object with the same prototype (class) and same own properties (including enumerability/writability/getters/setters etc):

这将创建一个具有相同原型(类)和相同属性(包括枚举/可写/ getter / setter等)的对象的副本:

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

(see here)

It doesn't necessarily work well for builtin objects. For example Array.isArray(clone([])) is false, and clone(function () {})() says it is not a function, but for user created objects (either class instances or object literals) it works fine.

它不一定适用于内置对象。例如,Array.isArray(clone([]))为false,而clone(function(){})()表示它不是函数,但对于用户创建的对象(类实例或对象文字),它可以正常工作。

To do a deep clone, you will have to loop over the property descriptors and clone the values recursively:

要进行深度克隆,您必须循环遍历属性描述符并以递归方式克隆值:

function deepClone(obj) {
  if (obj === null || typeof obj !== "object")
    return obj
  var props = Object.getOwnPropertyDescriptors(obj)
  for (var prop in props) {
    props[prop].value = deepClone(props[prop].value)
  }
  return Object.create(
    Object.getPrototypeOf(obj), 
    props
  )
}

#3


1  

I think that not necessarily needs to work with classes(or functions instances), you could extend prototype to apply OOP. My suggestion is that you extend prototype instead of create classes. jQuery is for DOM but not for advance object treatment so while $.extend could be helpful in some cases for complex stuff there are more advanced libraries.

我认为不一定需要使用类(或函数实例),您可以扩展原型以应用OOP。我的建议是你扩展原型而不是创建类。 jQuery适用于DOM但不适用于高级对象处理,因此虽然$ .extend在某些​​情况下对于复杂的东西有帮助,但是有更高级的库。

You could use libraries like CloneJS to easily work with extendable objects: https://npmjs.org/package/clonejs

您可以使用像CloneJS这样的库来轻松使用可扩展对象:https://npmjs.org/package/clonejs

Just include this script: http://quadroid.github.io/clonejs/cdn/clone.min.js

只需包含此脚本:http://quadroid.github.io/clonejs/cdn/clone.min.js

And try their own example:

并尝试自己的例子:

/// Forget about classes.    
//  Instead of creating class (function), create prototype (object):
var $duck = $object.clone({
    name: 'Unnamed',
    quack: function(){
        console.log( this.name +' Duck: Quack-quack!');
    }
});
$duck.quack();//Unnamed Duck: Quack-quack!

/// Inheritance is simple: 
var $talkingDuck = $duck.clone({
    quack: function(){
        this.applySuper('quack');
        console.log('My name is '+ this.name +'!');
    }       
});

/// Forget about the `new` operator, use .create() method instead:
var donald = $talkingDuck.create({name: 'Donald'});
donald.quack();// Donald Duck: Quack-quack! My name is Donald!

/// Forget about the `instanceof` operator, use JS native 
//  .isPrototypeOf() method instead:
$duck.isPrototypeOf(donald);// true

Also I think that Backbone.js applies the extension of prototype instead of creation of classes. They use _.extend

另外我认为Backbone.js应用原型的扩展而不是类的创建。他们使用_.extend

Some more references: http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend

更多参考文献:http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend

#4


1  

You should try something like this:

你应该尝试这样的事情:

function clone_object(o){
    var n=Object.create(
        Object.getPrototypeOf(o),
        Object.getOwnPropertyNames(o).reduce(
            function(prev,cur){
                prev[cur]=Object.getOwnPropertyDescriptor(o,cur);
                return prev;
            },
            {}
        )
    );
    if(!Object.isExtensible(o)){Object.preventExtensions(n);}
    if(Object.isSealed(o)){Object.seal(n);}
    if(Object.isFrozen(o)){Object.freeze(n);}

    return n;
}

Narrative:

  1. Create the new object using Object.create from a prototype and a properties object.
  2. 使用原型和属性对象中的Object.create创建新对象。

  3. For the prototype of the object to be cloned, use the prototype of the original object, using Object.getPrototypeOf.
  4. 对于要克隆的对象的原型,使用Object.getPrototypeOf使用原始对象的原型。

  5. To create the properties object, loop over the own properties of the original object (using getOwnPropertyNames), and retrieve the property descriptor for each using getOwnPropertyDescriptor.
  6. 要创建属性对象,请循环遍历原始对象的自身属性(使用getOwnPropertyNames),并使用getOwnPropertyDescriptor检索每个属性的描述符。

  7. Apply the extensibility/sealed/frozen characteristics of the original object to the clone.
  8. 将原始对象的可扩展性/密封/冻结特征应用于克隆。

This will not deep-clone properties whose values are themselves objects. That's left as an exercise to the reader...YMMV.

这不会深度克隆其值本身就是对象的属性。这留给了读者一个练习... YMMV。

#5


0  

I'd be interested to see someone benchmark this method against other approaches to cloning class instances in JavaScript that use native JavaScript.

我有兴趣看到有人将此方法与使用本机JavaScript的JavaScript中克隆类实例的其他方法进行对比。

// Defining a class
function MyClass(args) {

  this.foo = "bar";
}
// Avoid duplicate instances of class methods in RAM by adding them to the class prototype like this
MyClass.prototype.method = method;
MyClass.prototype.clone = clone;

// Define what your methods do
function method() {

  console.log(this.foo);
}

function clone() {

  var classScope = this;

  // Get the prototype of your class. This will create an object that has one key for every method in your class. I'm not sure if this will go up the prototype chain if you have subclasses. Someone ought to edit this answer to clarify that.
  var miniMe = Object.getPrototypeOf(this);

  // Iterate the properties of your class--all the internal key-value pairs that do get duplicated in RAM each time you instantiate the class.
  Object.keys(this).forEach(iterate);

  function iterate(key, index, list) {

      // Add each property to your clone
      miniMe[key] = classScope[key];
    }
    // Return the clone
  return miniMe;
}


// Instantiate your class
var me = new MyClass();
// Clone it
var miniMe = me.clone();

// Run some tests
Object.keys(Object.getPrototypeOf(me)).forEach(iterate);
Object.keys(me).forEach(iterate);

function iterate(property, index, list) {

  if (!miniMe.hasOwnProperty(property))
    throw new Error("miniMe does not have property " + property);
}

// Change the value of miniMe.foo and make sure it didn't impact the value of me.foo
miniMe.foo = "baz";
if (me.foo === miniMe.foo)
  throw new Error("me.foo should not equal miniMe.foo, because we changed its value");

#6


-1  

Edited: When you know what kind of object you want to clone you can follow my example:

编辑:当您知道要克隆的对象类型时,可以按照我的示例进行操作:

In your example you can simply do something like this:

在您的示例中,您可以简单地执行以下操作:

var child = new Child('Billy');
var clone = new Child();
for (var prop in child) { 
  clone[prop] = child[prop];
}

I have updated your jsFiddle

我已经更新了你的jsFiddle

#1


11  

How do I clone a JavaScript class instance?

如何克隆JavaScript类实例?

It's hardly possible if the instance was created with heavy use of closures in the constructor function. We may never now which internal values were set, and how to reproduce such a setup. Therefore, the easiest way would be if every class offered a clone function which knows what to do.

如果在构造函数中大量使用闭包创建实例,则几乎不可能。我们现在可能永远不会设置哪些内部值,以及如何重现这样的设置。因此,最简单的方法是每个类都提供克隆功能,知道该怎么做。

normal jQuery extend just returns a vanilla object

普通的jQuery扩展只返回一个vanilla对象

Not necessarily, it returns what you passed in. Use

不一定,它会返回您传入的内容。使用

var clone = $.extend(true, Object.create(Object.getPrototypeOf(child)), child);

instead and your instanceof usage will work fine. Note that the true signifies a "deep" copy which may or may not be what you want. Also, $.extend will happily copy enumerable inherited properties as well, so you might need to use a more sophisticated extend function.

相反,您的instanceof用法将正常工作。请注意,true表示“深层”副本,可能是您想要的,也可能不是。此外,$ .extend也会愉快地复制可枚举的继承属性,因此您可能需要使用更复杂的扩展函数。

Or without jQuery at all, and only copying own, enumerable properties and only using a shallow copy:

或者根本没有jQuery,只复制自己的,可枚举的属性,只使用浅拷贝:

var clone = Object.assign(Object.create(Object.getPrototypeOf(child)), child);

But again, not all objects will be clonable in this way, see my first point above.

但同样,并非所有对象都会以这种方式克隆,请参阅上面的第一点。

#2


3  

This will create a copy of an object with the same prototype (class) and same own properties (including enumerability/writability/getters/setters etc):

这将创建一个具有相同原型(类)和相同属性(包括枚举/可写/ getter / setter等)的对象的副本:

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

(see here)

It doesn't necessarily work well for builtin objects. For example Array.isArray(clone([])) is false, and clone(function () {})() says it is not a function, but for user created objects (either class instances or object literals) it works fine.

它不一定适用于内置对象。例如,Array.isArray(clone([]))为false,而clone(function(){})()表示它不是函数,但对于用户创建的对象(类实例或对象文字),它可以正常工作。

To do a deep clone, you will have to loop over the property descriptors and clone the values recursively:

要进行深度克隆,您必须循环遍历属性描述符并以递归方式克隆值:

function deepClone(obj) {
  if (obj === null || typeof obj !== "object")
    return obj
  var props = Object.getOwnPropertyDescriptors(obj)
  for (var prop in props) {
    props[prop].value = deepClone(props[prop].value)
  }
  return Object.create(
    Object.getPrototypeOf(obj), 
    props
  )
}

#3


1  

I think that not necessarily needs to work with classes(or functions instances), you could extend prototype to apply OOP. My suggestion is that you extend prototype instead of create classes. jQuery is for DOM but not for advance object treatment so while $.extend could be helpful in some cases for complex stuff there are more advanced libraries.

我认为不一定需要使用类(或函数实例),您可以扩展原型以应用OOP。我的建议是你扩展原型而不是创建类。 jQuery适用于DOM但不适用于高级对象处理,因此虽然$ .extend在某些​​情况下对于复杂的东西有帮助,但是有更高级的库。

You could use libraries like CloneJS to easily work with extendable objects: https://npmjs.org/package/clonejs

您可以使用像CloneJS这样的库来轻松使用可扩展对象:https://npmjs.org/package/clonejs

Just include this script: http://quadroid.github.io/clonejs/cdn/clone.min.js

只需包含此脚本:http://quadroid.github.io/clonejs/cdn/clone.min.js

And try their own example:

并尝试自己的例子:

/// Forget about classes.    
//  Instead of creating class (function), create prototype (object):
var $duck = $object.clone({
    name: 'Unnamed',
    quack: function(){
        console.log( this.name +' Duck: Quack-quack!');
    }
});
$duck.quack();//Unnamed Duck: Quack-quack!

/// Inheritance is simple: 
var $talkingDuck = $duck.clone({
    quack: function(){
        this.applySuper('quack');
        console.log('My name is '+ this.name +'!');
    }       
});

/// Forget about the `new` operator, use .create() method instead:
var donald = $talkingDuck.create({name: 'Donald'});
donald.quack();// Donald Duck: Quack-quack! My name is Donald!

/// Forget about the `instanceof` operator, use JS native 
//  .isPrototypeOf() method instead:
$duck.isPrototypeOf(donald);// true

Also I think that Backbone.js applies the extension of prototype instead of creation of classes. They use _.extend

另外我认为Backbone.js应用原型的扩展而不是类的创建。他们使用_.extend

Some more references: http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend

更多参考文献:http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend

#4


1  

You should try something like this:

你应该尝试这样的事情:

function clone_object(o){
    var n=Object.create(
        Object.getPrototypeOf(o),
        Object.getOwnPropertyNames(o).reduce(
            function(prev,cur){
                prev[cur]=Object.getOwnPropertyDescriptor(o,cur);
                return prev;
            },
            {}
        )
    );
    if(!Object.isExtensible(o)){Object.preventExtensions(n);}
    if(Object.isSealed(o)){Object.seal(n);}
    if(Object.isFrozen(o)){Object.freeze(n);}

    return n;
}

Narrative:

  1. Create the new object using Object.create from a prototype and a properties object.
  2. 使用原型和属性对象中的Object.create创建新对象。

  3. For the prototype of the object to be cloned, use the prototype of the original object, using Object.getPrototypeOf.
  4. 对于要克隆的对象的原型,使用Object.getPrototypeOf使用原始对象的原型。

  5. To create the properties object, loop over the own properties of the original object (using getOwnPropertyNames), and retrieve the property descriptor for each using getOwnPropertyDescriptor.
  6. 要创建属性对象,请循环遍历原始对象的自身属性(使用getOwnPropertyNames),并使用getOwnPropertyDescriptor检索每个属性的描述符。

  7. Apply the extensibility/sealed/frozen characteristics of the original object to the clone.
  8. 将原始对象的可扩展性/密封/冻结特征应用于克隆。

This will not deep-clone properties whose values are themselves objects. That's left as an exercise to the reader...YMMV.

这不会深度克隆其值本身就是对象的属性。这留给了读者一个练习... YMMV。

#5


0  

I'd be interested to see someone benchmark this method against other approaches to cloning class instances in JavaScript that use native JavaScript.

我有兴趣看到有人将此方法与使用本机JavaScript的JavaScript中克隆类实例的其他方法进行对比。

// Defining a class
function MyClass(args) {

  this.foo = "bar";
}
// Avoid duplicate instances of class methods in RAM by adding them to the class prototype like this
MyClass.prototype.method = method;
MyClass.prototype.clone = clone;

// Define what your methods do
function method() {

  console.log(this.foo);
}

function clone() {

  var classScope = this;

  // Get the prototype of your class. This will create an object that has one key for every method in your class. I'm not sure if this will go up the prototype chain if you have subclasses. Someone ought to edit this answer to clarify that.
  var miniMe = Object.getPrototypeOf(this);

  // Iterate the properties of your class--all the internal key-value pairs that do get duplicated in RAM each time you instantiate the class.
  Object.keys(this).forEach(iterate);

  function iterate(key, index, list) {

      // Add each property to your clone
      miniMe[key] = classScope[key];
    }
    // Return the clone
  return miniMe;
}


// Instantiate your class
var me = new MyClass();
// Clone it
var miniMe = me.clone();

// Run some tests
Object.keys(Object.getPrototypeOf(me)).forEach(iterate);
Object.keys(me).forEach(iterate);

function iterate(property, index, list) {

  if (!miniMe.hasOwnProperty(property))
    throw new Error("miniMe does not have property " + property);
}

// Change the value of miniMe.foo and make sure it didn't impact the value of me.foo
miniMe.foo = "baz";
if (me.foo === miniMe.foo)
  throw new Error("me.foo should not equal miniMe.foo, because we changed its value");

#6


-1  

Edited: When you know what kind of object you want to clone you can follow my example:

编辑:当您知道要克隆的对象类型时,可以按照我的示例进行操作:

In your example you can simply do something like this:

在您的示例中,您可以简单地执行以下操作:

var child = new Child('Billy');
var clone = new Child();
for (var prop in child) { 
  clone[prop] = child[prop];
}

I have updated your jsFiddle

我已经更新了你的jsFiddle