如何克隆 JavaScript 类实例?

IT技术 javascript jquery
2021-03-09 10:03:58

如何克隆 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 所有作为对象的属性也被克隆。

6个回答

如何克隆 JavaScript 类实例?

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

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

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

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

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

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

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
  )
}
你的 deepClone 在 window.Date 上为我工作。当 IE 不支持 JS 类时,我不能使用扩展。
2021-05-01 10:03:58
这就是我要找的。谢谢!
2021-05-16 10:03:58

你应该尝试这样的事情:

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. 要创建 properties 对象,请遍历原始对象的自己的属性( using getOwnPropertyNames),并为每个 using 检索属性描述符getOwnPropertyDescriptor
  4. 将原始对象的可扩展性/密封/冻结特性应用于克隆。

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

我认为不一定需要使用类(或函数实例),您可以扩展原型以应用 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

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