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


96

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

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

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

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


2
Если у вас есть новый ответ на старый вопрос о переполнении стека, добавьте этот ответ к исходному вопросу, а не просто создавайте новый.
Heretic Monkey

1
Я действительно вижу проблему, с которой / сталкивался Том, поскольку экземпляры классов ES6 работают иначе, чем «обычные» объекты.
CherryNerd

2
Кроме того, первый фрагмент кода в принятом ответе, который предоставляет ваш «возможный дубликат», на самом деле дает сбой, когда я пытаюсь запустить его поверх экземпляра класса
ES6

Я думаю, что это не дубликат, потому что, хотя экземпляр класса ES6 является объектом, не каждый объект является экземпляром класса ES6, и поэтому другой вопрос не касается проблемы этого вопроса.
Томаш Зато - Восстановите Монику

5
Это не дубликат. Другой вопрос касался чистых Objects, используемых в качестве хранилищ данных. Это о ES6 classи проблеме не потерять информацию о типе класса. Требуется другое решение.
Флори

Ответы:


111

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

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

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

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

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


1
Это не будет копировать статические методы, потому что они фактически не являются перечисляемыми собственными свойствами.
Мистер Лаваламп

5
@ Mr.Lavalamp, а как вы можете скопировать (также) статические методы?
Флори

это уничтожит массивы! Он преобразует все массивы в объекты с ключами «0», «1», ...
Вахид

1
@KeshaAntonov Возможно, вы сможете найти решение с помощью методов typeof и Array. Я сам предпочел клонировать все свойства вручную.
Вахид

1
Не ожидайте, что он клонирует свойства, которые сами являются объектами: jsbin.com/qeziwetexu/edit?js,console
jduhls

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

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

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


2
Поскольку Object.createсоздает новый объект с указанным прототипом, почему бы не просто const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Также будут скопированы методы класса.
barbatus

1
@barbatus Тем не менее, он использует неправильный прототип Blah.prototype != instanceOfBlah. Вы должны использоватьObject.getPrototypeOf(instanceOfBlah)
Берги

1
@Bergi нет, экземпляр класса ES6 не всегда имеет прототип. Проверьте codepen.io/techniq/pen/qdZeZm , он также работает с экземпляром.
barbatus

@barbatus Извините, что? Я не слежу. У всех экземпляров есть прототип, что и делает их экземплярами. Попробуйте код из ответа Флори.
Берги

1
@Bergi Я думаю, это зависит от конфигурации Babel или чего-то еще. Прямо сейчас я реализую реактивное собственное приложение, и экземпляры без унаследованных свойств имеют там прототип null. Также, как вы можете видеть здесь, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... возможно, что getPrototypeOf возвращает значение null.
barbatus

3

Не рекомендуется делать расширения Прототипа, это приведет к проблемам, когда вы будете делать тесты своего кода / компонентов. Фреймворки модульного тестирования не будут автоматически принимать расширения вашего прототипа. Так что это плохая практика. Здесь есть больше объяснений расширений прототипов. Почему расширение собственных объектов - плохая практика?

Для клонирования объектов в JavaScript нет простого и понятного способа. Вот первый пример, использующий «Мелкую копию»:

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');
let clone =  Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) //  the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned

Полученные результаты:

original.logFullName ():

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

clone.logFullName ():

результат: Джон Сеффрин

original.address.street;

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

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

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

Подчеркивание: let cloneUnderscore = _ (оригинал) .clone ();

Клон Loadash: var cloneLodash = _.cloneDeep (оригинал);

Обратной стороной lodash или подчеркивания была необходимость включения в ваш проект дополнительных библиотек. Однако они являются хорошими вариантами и также дают высокие результаты производительности.


1
При назначении {}клон не унаследует ни один из методов прототипа оригинала. clone.logFullName()вообще не будет работать. То, Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)что у вас было раньше, было в порядке, почему вы изменили это?
Берги

1
@Bergi благодарит вас за ваш вклад, я редактировал свой ответ прямо сейчас, я добавил вашу точку зрения, чтобы скопировать прототипы!
Кассио Сеффрин

1
Я ценю вашу помощь @Bergi, пожалуйста, поделитесь своим мнением сейчас. Я закончил издание. Думаю, теперь ответ охватил почти весь вопрос. Спасибо!
Кассио Сеффрин

1
Да и просто вроде Object.assign({},original)не работает.
Берги

1
иногда все, что нам нужно, - это более простой подход. Если вам не нужны прототипы и сложные объекты, просто "clone = {... original}" может решить проблему
Кассио Сеффрин

0

Еще один лайнер:

В большинстве случаев ... (работает для Date, RegExp, Map, String, Number, Array), btw, клонирование строки, number немного забавно.

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

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

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

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

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


-4

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

let clone = { ...obj};

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

let clone = { ...obj, change: "something" };
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.