Jak sklonować instancję klasy javascript ES6

103

Jak sklonować instancję klasy JavaScript przy użyciu ES6.

Nie interesują mnie rozwiązania oparte na jquery czy $ extend.

Widziałem dość stare dyskusje na temat klonowania obiektów, które sugerują, że problem jest dość skomplikowany, ale w ES6 pojawia się bardzo proste rozwiązanie - zamieszczę je poniżej i zobaczę, czy ludzie uważają, że jest zadowalające.

edycja: sugeruje się, że moje pytanie jest duplikatem; Widziałem tę odpowiedź, ale ma 7 lat i obejmuje bardzo skomplikowane odpowiedzi przy użyciu js z wersji wcześniejszej niż ES6. Sugeruję, że moje pytanie, które pozwala na ES6, ma znacznie prostsze rozwiązanie.

Tomek
źródło
2
Jeśli masz nową odpowiedź na stare pytanie dotyczące przepełnienia stosu, dodaj tę odpowiedź do pierwotnego pytania, a nie tylko utwórz nowe.
Heretic Monkey
1
Widzę problem, z którym boryka się Tom, ponieważ instancje klasy ES6 działają inaczej niż „zwykłe” obiekty.
CherryNerd
2
Ponadto, pierwszy fragment kodu w zaakceptowanej odpowiedzi, w której Twój „możliwy duplikat” zawiera informacje, faktycznie ulega awarii, gdy próbuję uruchomić go na instancji klasy ES6
CherryNerd
Myślę, że to nie jest duplikat, ponieważ chociaż instancja klasy ES6 jest obiektem, nie każdy obiekt jest instancją klasy ES6, a zatem drugie pytanie nie rozwiązuje problemu tego pytania.
Tomáš Zato - Przywróć Monikę
5
To nie jest duplikat. Drugie pytanie dotyczyło czystych Objects wykorzystywanych jako posiadacze danych. Ten dotyczy ES6 classi problemu, aby nie stracić informacji o typie klasy. Potrzebuje innego rozwiązania.
flori

Odpowiedzi:

118

To skomplikowane; Bardzo się starałem! Ostatecznie ten jeden wiersz zadziałał dla moich niestandardowych instancji klasy ES6:

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

Unika ustawiania prototypu, ponieważ mówi się, że bardzo spowalnia kod.

Obsługuje symbole, ale nie jest idealny dla metod pobierających / ustawiających i nie działa z niewliczalnymi właściwościami (patrz dokumentacja Object. assign () ). Ponadto klonowanie podstawowych klas wewnętrznych (takich jak Array, Date, RegExp, Map itp.) Niestety często wydaje się wymagać indywidualnej obsługi.

Wniosek: to bałagan. Miejmy nadzieję, że pewnego dnia pojawi się natywna i czysta funkcjonalność klonowania.

flori
źródło
1
Nie spowoduje to skopiowania metod statycznych, ponieważ w rzeczywistości nie są one wyliczalnymi własnymi właściwościami.
Pan Lavalamp
5
@ Mr. Lavalamp i jak można skopiować (także) metody statyczne?
flori
to zniszczy tablice! Konwertuje wszystkie tablice na obiekty z kluczami „0”, „1”,….
Vahid,
1
@KeshaAntonov Możesz znaleźć rozwiązanie za pomocą metod typeof i Array. Sam wolałem klonować wszystkie właściwości ręcznie.
Vahid
1
Nie spodziewaj się, że sklonuje właściwości, które same są obiektami: jsbin.com/qeziwetexu/edit?js,console
jduhls
12
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Zwróć uwagę na charakterystykę Object. assign : wykonuje on płytką kopię i nie kopiuje metod klas.

Jeśli chcesz mieć głęboką kopię lub większą kontrolę nad kopią, dostępne są funkcje klonowania lodash .

Tomek
źródło
2
Skoro Object.createtworzy nowy obiekt z określonym prototypem, to czemu nie po prostu const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Kopiowane będą również metody klasowe.
barbatus
1
@barbatus który wykorzystuje źle prototyp choć Blah.prototype != instanceOfBlah. Powinieneś użyćObject.getPrototypeOf(instanceOfBlah)
Bergi
1
@Bergi nie, instancja klasy ES6 nie zawsze ma prototyp. Sprawdź codepen.io/techniq/pen/qdZeZm , że działa również z instancją.
barbatus
@barbatus Przepraszam, co? Nie nadążam. Wszystkie instancje mają prototyp, dlatego są instancjami. Wypróbuj kod z odpowiedzi flori.
Bergi
1
@Bergi Myślę, że to zależy od konfiguracji Babel czy czegoś takiego. W tej chwili wdrażam reaktywną aplikację natywną, a wystąpienia bez dziedziczonych właściwości mają tam prototyp null. Jak widać tutaj developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... możliwe, że getPrototypeOf zwraca wartość null.
barbatus
4

Nie jest zalecane tworzenie rozszerzeń Prototypu, spowoduje to problemy podczas wykonywania testów na kodzie / komponentach. Struktury testów jednostkowych nie przyjmą automatycznie twoich prototypowych rozszerzeń. Więc to nie jest dobra praktyka. Więcej wyjaśnień dotyczących prototypowych rozszerzeń jest tutaj. Dlaczego rozszerzanie obiektów natywnych jest złą praktyką?

Nie ma prostego ani prostego sposobu na klonowanie obiektów w JavaScript. Oto pierwszy przypadek użycia „Shallow Copy”:

1 -> Płytki klon:

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

Wyniki:

original.logFullName ():

wynik: Cassio Seffrin

clone.logFullName ():

wynik: John Seffrin

original.address.street;

wynik: 'Street B, 99' // zauważ, że oryginalny obiekt podrzędny został zmieniony

Uwaga: Jeśli instancja ma domknięcia jako własne właściwości, ta metoda jej nie zawinie. ( przeczytaj więcej o domknięciach ) Ponadto podobiekt „address” nie zostanie sklonowany.

clone.logFullName ()

nie będzie działać.

cloneWithPrototype.logFullName ()

zadziała, ponieważ klon skopiuje również swoje prototypy.

Aby sklonować tablice za pomocą Object. assign:

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

Klonuj macierz za pomocą sintax rozszerzania ECMAScript:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> Głęboki klon:

Aby zarchiwizować zupełnie nowe odniesienie do obiektu, możemy użyć JSON.stringify () do przeanalizowania oryginalnego obiektu jako ciągu znaków, a po przeanalizowaniu go z powrotem do JSON.parse ().

let deepClone = JSON.parse(JSON.stringify(original));

Dzięki głębokiemu klonowaniu odniesienia do adresu zostaną zachowane. Jednak prototypy deepClone zostaną utracone, dlatego metoda deepClone.logFullName () nie będzie działać.

3 -> Biblioteki stron trzecich:

Inną opcją będzie użycie bibliotek innych firm, takich jak loadash lub podkreślenie. Tworzą nowy obiekt i kopiują każdą wartość z oryginału do nowego obiektu, zachowując odniesienia w pamięci.

Podkreślenie: niech cloneUnderscore = _ (oryginał) .clone ();

Loadash clone: ​​var cloneLodash = _.cloneDeep (oryginał);

Wadą lodash lub podkreślenia była konieczność włączenia do projektu kilku dodatkowych bibliotek. Są to jednak dobre opcje, a także zapewniają wysoką wydajność.

Cassio Seffrin
źródło
1
Podczas przypisywania do {}, klon nie odziedziczy żadnej z prototypowych metod oryginału. clone.logFullName()w ogóle nie zadziała. To Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal), co miałeś wcześniej, było w porządku, dlaczego to zmieniłeś?
Bergi
1
@Bergi dziękuję za twój wkład, właśnie redagowałem moją odpowiedź, dodałem twój punkt, aby skopiować prototypy!
Cassio Seffrin
1
Doceniam twoją pomoc @Bergi, proszę, podziel się teraz swoją opinią. Zakończyłem edycję. Myślę, że teraz odpowiedź obejmowała prawie wszystkie pytania. Dzięki!
Cassio Seffrin
1
Tak, i po prostu Object.assign({},original)nie działa.
Bergi
1
czasami wystarczy prostsze podejście. Jeśli nie potrzebujesz prototypów i złożonych obiektów, może po prostu "clone = {... original}" może rozwiązać problem
Cassio Seffrin
0

Kolejna wkładka:

W większości przypadków ... (działa dla Date, RegExp, Map, String, Number, Array), btw, cloning string, number jest trochę zabawne.

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

dla tych klas bez konstruktora kopiującego:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
Eric
źródło
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;
}, {});

wprowadź opis obrazu tutaj

Alex Ivasyuv
źródło
-1

Lubię prawie wszystkie odpowiedzi. Miałem ten problem i aby go rozwiązać, robiłem to ręcznie definiując clone()metodę, a wewnątrz niej budowałbym cały obiekt od podstaw. Dla mnie ma to sens, ponieważ wynikowy obiekt będzie naturalnie tego samego typu co sklonowany obiekt.

Przykład z maszynopisem:

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

Podoba mi się to rozwiązanie, ponieważ wygląda bardziej „obiektowo”

Hassan Ali Salem
źródło
-3

Możesz użyć operatora rozprzestrzeniania, na przykład, jeśli chcesz sklonować obiekt o nazwie Obj:

let clone = { ...obj};

A jeśli chcesz zmienić lub dodać cokolwiek do sklonowanego obiektu:

let clone = { ...obj, change: "something" };
ttfreeman
źródło