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

Как клонировать экземпляр класса Javascript с помощью ES6.

Меня не интересуют решения, основанные на jquery или $extend.

Я видел довольно старые обсуждения клонирования объектов, которые предполагают, что проблема довольно сложна, но с ES6 появляется очень простое решение - я покажу его ниже и посмотрю, считают ли люди его удовлетворительным.

редактировать: предполагается, что мой вопрос является дубликатом; Я видел этот ответ, но ему 7 лет, и он включает в себя очень сложные ответы с использованием js до ES6. Я предполагаю, что мой вопрос, который позволяет использовать ES6, имеет гораздо более простое решение.


person Tom    schedule 04.01.2017    source источник
comment
Если у вас есть новый ответ на старый вопрос о Stack Overflow, добавьте этот ответ к исходному вопросу, а не просто создавайте новый.   -  person Heretic Monkey    schedule 05.01.2017
comment
Я вижу проблему, с которой столкнулся Том, поскольку экземпляры класса ES6 работают иначе, чем обычные объекты.   -  person CherryNerd    schedule 05.01.2017
comment
Кроме того, первый фрагмент кода в принятом ответе, который ваш возможный дубликат предоставляет, фактически дает сбой, когда я пытаюсь запустить его над экземпляром класса ES6.   -  person CherryNerd    schedule 05.01.2017
comment
Я думаю, что это не дубликат, потому что, хотя экземпляр класса ES6 является объектом, не каждый объект является экземпляром класса ES6, и поэтому другой вопрос не касается проблемы этого вопроса.   -  person Tomáš Zato - Reinstate Monica    schedule 28.10.2017
comment
Это не дубликат. Другой вопрос касался чистых Object, используемых в качестве держателей данных. Это касается ES6 classes и проблемы с тем, чтобы не потерять информацию о типе класса. Нужно другое решение.   -  person flori    schedule 28.10.2017
comment
Повторное повторное голосование. Экземпляры класса не обязательно совпадают с обычными объектами.   -  person jhpratt    schedule 21.09.2018
comment
Это вообще не дубликат. По крайней мере, не на связанный вопрос. Голосование за открытие   -  person vir us    schedule 30.05.2019


Ответы (9)


Это сложно; Я много пробовал! В конце концов, этот однострочный код работал для моих пользовательских экземпляров класса ES6:

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

Он избегает установки прототипа, потому что так говорят сильно замедляет код.

Он поддерживает символы, но не идеален для геттеров/сеттеров и не работает с неперечислимыми свойствами (см. документы Object.assign()). Кроме того, клонирование основных внутренних классов (таких как Array, Date, RegExp, Map и т. д.), к сожалению, часто требует индивидуальной обработки.

Вывод: это бардак. Будем надеяться, что однажды появится нативная и чистая функциональность клонирования.

person flori    schedule 27.06.2017
comment
Это не будет копировать статические методы, потому что они на самом деле не являются перечисляемыми собственными свойствами. - person Mr. Lavalamp; 22.12.2017
comment
@Mr.Lavalamp, а как вы можете копировать (также) статические методы? - person flori; 23.12.2017
comment
это уничтожит массивы! Он преобразует все массивы в объекты с ключами 0,1,.... - person Vahid; 19.10.2018
comment
@vmoh_ir как с этим бороться? - person Kesha Antonov; 21.10.2018
comment
@KeshaAntonov Возможно, вы сможете найти решение с помощью методов typeof и Array. Я сам предпочитал клонировать все свойства вручную. - person Vahid; 21.10.2018
comment
Не ожидайте, что он будет клонировать свойства, которые сами являются объектами: jsbin.com/qeziwetexu/edit?js, консоль - person jduhls; 13.11.2018
comment
Статический метод не нужно клонировать! они часть класса, а не экземпляр - person pery mimon; 10.01.2019
comment
Эта штука сделает orig прототипом вновь созданного объекта. Подумайте о том, чтобы сделать Object.create(orig.__proto__) вместо Object.create(orig), иначе, если вы повторите это 100 раз, вы закончите цепочку прототипов длиной более 100. - person Heimdell; 26.01.2019
comment
@Heimdell На самом деле он уже делает Object.create(orig.getPrototypeOf()), что равно вашему Object.create(orig.__proto__) - person flori; 27.01.2019
comment
Не копирует неперечислимые свойства, плохо работает с геттерами и сеттерами, не копирует свойства символов, не клонирует не примитивные члены (так что они фактически могут делиться своими данными), не работает с Date, RegExp, Set, Map, ... Если orig = [1,2,3], то clone.length === 0. Не запускать конструктор во многих случаях проблематично. - person trincot; 29.12.2019
comment
@trincot Спасибо, я добавил примечания относительно упомянутых вами ограничений. Символы, кажется, работают нормально (?) Поверхностное клонирование, как я бы сказал, как и ожидалось (?) - person flori; 31.12.2019

const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Обратите внимание на характеристики Object.assign: делает поверхностную копию и не копирует методы класса.

Если вам нужна глубокая копия или больший контроль над копией, воспользуйтесь функциями клонирования lodash. .

person Tom    schedule 04.01.2017
comment
Так как Object.create создает новый объект с указанным прототипом, почему бы тогда просто const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Также будут скопированы методы класса. - person barbatus; 19.02.2017
comment
@barbatus Однако здесь используется неправильный прототип, Blah.prototype != instanceOfBlah. Вы должны использовать Object.getPrototypeOf(instanceOfBlah) - person Bergi; 15.07.2017
comment
@Bergi Берги нет, экземпляр класса ES6 не всегда имеет прототип. Проверьте codepen.io/techniq/pen/qdZeZm, что он работает и с экземпляром. - person barbatus; 02.09.2017
comment
@barbatus Извините, что? Я не понимаю. У всех экземпляров есть прототип, вот что делает их экземплярами. Попробуйте код из ответа Флори. - person Bergi; 02.09.2017
comment
@Bergi Берги Я думаю, это зависит от конфигурации Babel или чего-то еще. Сейчас я реализую реактивное собственное приложение, и экземпляры без унаследованных свойств имеют нулевой прототип. Также, как вы можете видеть здесь, developer.mozilla. org/en-US/docs/Web/JavaScript/Reference/ возможно, что getPrototypeOf возвращает значение null. - person barbatus; 02.09.2017
comment
@barbatus Но никогда для объекта, который был создан с помощью new из обычного class. И даже тогда вы бы хотели, чтобы у клона был такой же прототип… - person Bergi; 02.09.2017
comment
@Bergi, чем тогда отличаются прототипы объектов, созданных Object.create(instance) и Object.create(Object.getPrototypeOf(instance)) (учитывая, что они предназначены для клонирования)? - person barbatus; 06.09.2017
comment
@barbatus Разве не очевидно, что они разные? Если вы используете экземпляр, а затем клонируете клон клона клона и т. д., вы получаете бесконечно длинную цепочку прототипов для последнего из них, которая включает в себя все остальные. Они будут вести себя совершенно иначе, чем клоны, которые просто используют тот же оригинальный прототип. - person Bergi; 06.09.2017
comment
Как насчет Object.assign(Object.create(Object.getPrototypeOf(instanceOfBlah)), instanceOfBlah)? Он не делает глубокого клонирования, но прекрасно работает для большинства целей. - person Toothbrush; 17.02.2018

Мне нравятся почти все ответы. У меня была эта проблема, и чтобы решить ее, я бы сделал это вручную, определив метод 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);
    }
}

Мне нравится это решение, потому что оно выглядит более «объектно-ориентированным».

person HSLM    schedule 26.11.2020

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 не рекомендуется делать расширения прототипа, это приведет к проблемам, когда вы будете тестировать свой код/компоненты. Фреймворки модульного тестирования не будут автоматически принимать расширения вашего прототипа. Так что это не очень хорошая практика. Здесь есть больше объяснений расширений прототипа ?

Для клонирования объектов в JavaScript нет простого или прямого способа. Вот первый пример использования Shallow Copy:

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

Результаты:

оригинал.logFullName();

результат: Кассио Сеффрин

cloneWithPrototype.logFullName();

результат: Кассио Сеффрин

исходный.адрес.улица;

результат: 'Улица B, 99' // обратите внимание, что исходный подобъект был изменен

Примечание. Если у экземпляра есть замыкания как собственные свойства, этот метод не будет его обертывать. (узнайте больше о замыканиях). Кроме того, адрес подобъекта будет не получить клон.

cloneWithoutPrototype.logFullName()

Не будет работать. Клон не наследует ни один из методов прототипа оригинала.

cloneWithPrototype.logFullName()

будет работать, потому что клон также будет копировать свои прототипы.

Чтобы клонировать массивы с помощью Object.assign:

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

Клонировать массив, используя синтаксис распространения ECMAScript:

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 или подчеркивания была необходимость включать в проект дополнительные библиотеки. Однако они являются хорошими вариантами и также дают высокие результаты производительности.

person Cassio Seffrin    schedule 27.12.2019
comment
Было бы лучше, если бы вы редактировали свой предыдущий ответ вместо его удаления и повторной публикации. - person Bergi; 31.12.2019
comment
При назначении {} клон не наследует ни один из методов прототипа оригинала. clone.logFullName() вообще работать не будет. Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal), который у вас был раньше, был в порядке, почему вы изменили его? - person Bergi; 31.12.2019
comment
@Bergi, спасибо за ваш вклад, я редактировал свой ответ прямо сейчас, я добавил ваш пункт, чтобы скопировать прототипы! - person Cassio Seffrin; 31.12.2019
comment
Я ценю вашу помощь @Bergi, пожалуйста, выскажите свое мнение сейчас. Я закончил выпуск. Я думаю, что теперь ответ покрыл почти весь вопрос. Спасибо! - person Cassio Seffrin; 31.12.2019
comment
Они такие же, как Object.assign({},original), но с более коротким синтаксисом, я думаю, что теперь все варианты были раскрыты в ответе. - person Cassio Seffrin; 31.12.2019
comment
Да и так же, как Object.assign({},original), не работает. - person Bergi; 31.12.2019
comment
иногда более простой подход — это все, что нам нужно. Если вам не нужны прототипы и сложные объекты, вы можете просто клонировать = { ...original } может решить проблему - person Cassio Seffrin; 31.12.2019
comment
IMO lodash — лучший вариант для клонирования объектов в Javascript. Однако в Vanilla JS я выберу метод cloneWithoutPrototype, как описано в этом ответе. - person ; 20.01.2021

Еще один вкладыш:

Большую часть времени... (работает для даты, регулярного выражения, карты, строки, числа, массива), кстати, клонирование строки, число немного забавно.

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

для тех классов без конструктора копирования:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
person Eric    schedule 23.07.2020

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;
}, {});

введите здесь описание изображения

person Alex Ivasyuv    schedule 17.08.2020

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

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

Работает с неперечислимыми свойствами, геттерами, сеттерами и т. д. Невозможно клонировать внутренние слоты, которые есть во многих встроенных типах javascript (например, массив, карта, прокси).

person TheNumberOne    schedule 27.02.2021

Попробуй это:

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 = obj[key];
       result[key] = value;
   }

   Object.setPrototypeOf(result, obj.__proto__);
   return result;
}

//test
function Point(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
);
Since it uses Object.getOwnPropertyNames, it will also add non-enumerable properties.

person Nirvana    schedule 19.04.2021

Вы можете использовать оператор распространения, например, если хотите клонировать объект с именем Obj:

let clone = { ...obj};

И если вы хотите что-то изменить или добавить к клонированному объекту:

let clone = { ...obj, change: "something" };
person ttfreeman    schedule 08.05.2020