Подтвердить что ты не робот

Как клонировать экземпляр класса JavaScript?

Как мне клонировать экземпляр класса 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/

Я бы предпочел, чтобы клон был глубоким/рекурсивным. И.Е. все объекты, которые являются объектами, также клонируются.

4b9b3361

Ответ 1

Как мне клонировать экземпляр класса JavaScript?

Это вряд ли возможно, если экземпляр был создан при интенсивном использовании замыканий в функции конструктора. Мы никогда не можем теперь установить, какие внутренние значения были установлены, и как воспроизвести такую ​​настройку. Поэтому самым простым способом было бы, если бы каждый класс предложил функцию clone который знает, что делать.

обычный jQuery extend просто возвращает объект ванили

Не обязательно, он возвращает то, что вы передали. Используйте

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

а ваше использование instanceof будет работать нормально. Тем не менее, $.extend с удовольствием скопирует перечислимые унаследованные свойства, поэтому вам может понадобиться использовать более сложную функцию extend.

Ответ 2

Это создаст копию объекта с тем же прототипом (классом) и теми же собственными свойствами (включая перечисление/запись/геттеры/сеттеры и т.д.):

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

(см. здесь)

Это не обязательно хорошо работает для встроенных объектов. Например, Array.isArray(clone([])) - 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
  )
}

Ответ 3

Я думаю, что не обязательно работать с классами (или экземплярами функций), вы можете расширить прототип для применения ООП. Мое предложение состоит в том, что вы расширяете прототип вместо создания классов. jQuery для DOM, но не для обработки предварительных объектов, тогда как $.extend может быть полезным в некоторых случаях для сложных вещей, есть более сложные библиотеки.

Вы можете использовать библиотеки, такие как CloneJS, для простой работы с расширяемыми объектами: https://npmjs.org/package/clonejs

Просто включите этот script: 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

Ответ 4

Вы должны попробовать что-то вроде этого:

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

Повествование:

  • Создайте новый объект с помощью Object.create из прототипа и объекта свойств.
  • Для прототипа клонируемого объекта используйте прототип исходного объекта, используя Object.getPrototypeOf.
  • Чтобы создать объект свойств, выполните цикл над собственными свойствами исходного объекта (используя getOwnPropertyNames) и извлеките дескриптор свойства для каждого с помощью getOwnPropertyDescriptor.
  • Примените свойства к расширяемости/запакованные/замороженные исходные объекты к клону.

Это не будет глубоко клонировать свойства, значения которых сами являются объектами. Это осталось как упражнение для читателя... YMMV.

Ответ 5

Мне было бы интересно увидеть, что кто-то сравнивает этот метод с другими подходами к клонированию экземпляров класса в 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

Отредактировано. Когда вы знаете, какой объект вы хотите клонировать, вы можете следовать моему примеру:

В вашем примере вы можете просто сделать что-то вроде этого:

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

Я обновил jsFiddle