Як клонувати екземпляр класу javascript ES6


96

Як клонувати екземпляр класу Javascript за допомогою ES6.

Мене не цікавлять рішення на основі jquery або $ extension.

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

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


2
Якщо у вас є нова відповідь на старе запитання на Stack Overflow, додайте цю відповідь до початкового питання, а не просто створюйте нове.
Єретична мавпа

1
Я бачу проблему, з якою стикався / стикався Том, оскільки екземпляри класу ES6 працюють не так, як "звичайні" Об'єкти.
CherryNerd

2
Крім того, перший фрагмент коду у прийнятій відповіді, який надає ваш "можливий дублікат", насправді дає збій, коли я намагаюся запустити його на екземплярі класу
ES6

Я думаю, що це не дублікат, оскільки, хоча екземпляр класу ES6 є об’єктом, не кожен об’єкт є екземпляром класу ES6, і тому інше питання не стосується проблеми цього питання.
Томаш Зато - відновити Моніку

5
Це не дублікат. Інше питання стосувалося чистих Objects, що використовуються як власники даних. Це про ES6 classes та проблему не втратити інформацію про тип класу. Потрібно інше рішення.
flori

Відповіді:


111

Це складно; Я багато пробував! Врешті-решт, цей одношаровий вкладиш працював для моїх власних екземплярів класу ES6:

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

Це дозволяє уникнути встановлення прототипу, оскільки вони кажуть це сильно уповільнює код.

Він підтримує символи, але не ідеально підходить для геттерів / сеттерів і не працює з незліченними властивостями (див. Object.assign () ). Крім того, клонування основних внутрішніх класів (таких як Array, Date, RegExp, Map тощо), на жаль, часто, здається, потребує індивідуальної обробки.

Висновок: це безлад. Будемо сподіватися, що колись з’явиться рідна та чиста функція клонування.


1
Це не буде копіювати статичні методи, оскільки вони насправді не є власними властивостями.
Містер Лаваламп,

5
@ Mr.Lavalamp, і як ви можете скопіювати (також) статичні методи?
flori

це зруйнує масиви! Він перетворить усі масиви на об'єкти з клавішами "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 02

@barbatus Вибачте, що? Я не слідкую. Усі екземпляри мають прототип, ось що робить їх екземплярами. Спробуйте код із відповіді flori.
Бергі

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;

результат: 'Вулиця Б, 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 -> 3-ті партійні бібліотеки:

Іншими варіантами буде використання сторонніх бібліотек, таких як loadash або підкреслення. Вони створюють новий об'єкт і копіюють кожне значення з оригіналу на новий об'єкт, зберігаючи його посилання в пам'яті.

Підкреслення: нехай cloneUnderscore = _ (оригінал) .clone ();

Клон завантаження: var cloneLodash = _.cloneDeep (оригінал);

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


1
Призначаючи {}, клон не успадковує жодного з методів-прототипів оригіналу. clone.logFullName()не буде працювати взагалі. Те, що у Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)вас було раніше, було добре, чому ви це змінили?
Берги

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

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

1
Так, і точно так само Object.assign({},original), це не працює.
Берги

1
іноді нам потрібен простіший підхід. Якщо вам не потрібні прототипи та складні об'єкти, можливо, просто "clone = {... original}" може вирішити проблему
Кассіо Сеффрін,

0

Ще один лайнер:

Найчастіше ... (працює з датою, RegExp, Map, String, Number, Array), до речі, рядок клонування, число трохи забавне.

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.