21

如何克隆 JavaScript 类实例?

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

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/

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

4

6 回答 6

13

如何克隆 JavaScript 类实例?

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

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

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

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

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

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

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

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

于 2013-04-15T22:15:17.593 回答
9

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

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

(见这里

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

要进行深度克隆,您必须遍历属性描述符并递归地克隆值:

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
  )
}
于 2017-05-03T07:09:30.283 回答
2

你应该尝试这样的事情:

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;
}

叙述:

  1. Object.create使用原型和属性对象创建新对象。
  2. 对于要克隆的对象的原型,使用原始对象的原型,使用Object.getPrototypeOf.
  3. 要创建属性对象,请遍历原始对象(使用getOwnPropertyNames)自己的属性,并检索每个使用的属性描述符getOwnPropertyDescriptor
  4. 将原始对象的可扩展性/密封/冻结特性应用于克隆。

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

于 2013-04-16T02:22:36.227 回答
1

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

您可以使用 CloneJS 之类的库轻松处理可扩展对象: https ://npmjs.org/package/clonejs

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

并尝试他们自己的例子:

/// 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

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

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

于 2013-04-16T00:56:13.800 回答
1

我很想看到有人将此方法与其他在使用本机 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");

于 2015-10-09T02:44:04.977 回答
-1

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

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

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

我已经更新了你的jsFiddle

于 2013-04-15T23:54:06.263 回答