Czy to dobry sposób na sklonowanie obiektu w ES6?

155

Wyszukiwanie w Google „obiektu klonowania javascript” przynosi naprawdę dziwne wyniki, niektóre z nich są beznadziejnie przestarzałe, a niektóre po prostu zbyt złożone, czyż nie jest to tak proste, jak po prostu:

let clone = {...original};

Czy jest w tym coś nie tak?

Dmitry Fadeev
źródło
1
to nie jest legalne ES6. Ale jeśli był, to nie jest klon: zarówno twój klon, jak i oryginalne właściwości wskazują teraz na te same rzeczy. Na przykład original = { a: [1,2,3] }daje klon z clone.adosłownie bytem original.a. Modyfikacja poprzez jedną clonelub originalmodyfikację tego samego , więc nie, to jest złe =)
Mike 'Pomax' Kamermans
2
@AlbertoRivera To trochę poprawny JavaScript, ponieważ jest to propozycja etapu 2, która prawdopodobnie będzie w przyszłości dodatkiem do standardu JavaScript.
Frxstrem
@Frxstrem z pytaniem dotyczącym ES6, to nie jest poprawne JavaScript =)
Mike 'Pomax' Kamermans
3
Klonowanie płytkie czy głębokie?
Felix Kling
2
Masz rację, to nie jest poprawne ES6, to jest poprawne ES9 . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Odpowiedzi:

240

Jest to dobre do płytkiego klonowania . Rozkład obiektów jest standardową częścią ECMAScript 2018 .

Do głębokiego klonowania potrzebujesz innego rozwiązania .

const clone = {...original} płytkie klonowanie

const newobj = {...original, prop: newOne} aby niezmiennie dodać kolejny rekwizyt do oryginału i zapisać jako nowy obiekt.

Mark Shust z M.academy
źródło
18
Jednak czy nie jest to tylko płytki klon? Tak jak w przypadku właściwości nie są klonowane rekurencyjnie, prawda? Dlatego też original.innerObject === clone.innerObject i zmiana właściwości original.innerObject.właściwość zmieni clone.innerObject.property.
milanio
18
tak, to jest płytki klon. jeśli chcesz mieć głębokiego klona, ​​musisz użyćJSON.parse(JSON.stringify(input))
Mark Shust z M.academy
8
/! \ JSON.parse (JSON.stringify (input)) zmienia daty, niezdefiniowane, ... To nie jest srebrna kropka do klonowania! Zobacz: maxpou.fr/immutability-js-without-library
Guillaume
1
Czy więc hack JSON.stringify () / JSON.parse () jest naprawdę zalecanym sposobem głębokiego klonowania obiektu w ES6? Ciągle widzę to zalecane. Niepokojące.
Solvitieg
3
@MarkShust JSON.parse(JSON.stringify(input))nie zadziała, ponieważ jeśli istnieją functionslub infinityjako wartości, po prostu przypisze nullich miejsce. Będzie działać tylko wtedy, gdy wartości są proste, literalsa nie functions.
ukośnik odwrotny
66

EDYCJA: Kiedy opublikowano tę odpowiedź, {...obj}składnia nie była dostępna w większości przeglądarek. W dzisiejszych czasach powinieneś sobie z nim radzić (chyba że musisz wspierać IE 11).

Użyj Object. assign.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

Jednak to nie będzie głębokim klonem. Jak dotąd nie ma natywnego sposobu głębokiego klonowania.

EDYCJA: Jak @Mike 'Pomax' Kamermans wspomniał w komentarzach, możesz głęboko klonować proste obiekty (tj. Bez prototypów, funkcji lub odwołań cyklicznych) za pomocą JSON.parse(JSON.stringify(input))

Alberto Rivera
źródło
19
Jest jeden, pod warunkiem, że twój obiekt jest prawdziwym literałem obiektu i czysto danymi, w którym JSON.parse(JSON.stringify(input))to przypadku jest właściwym głębokim klonem. Jednak w chwili, gdy w grę wchodzą prototypy, funkcje lub odwołania cykliczne, rozwiązanie to przestaje działać.
Mike 'Pomax' Kamermans
@ Mike'Pomax'Kamermans To prawda. Utrata funkcjonalności dla getterów i seterów jest jednak straszna ...
Alberto Rivera
Jeśli potrzebujesz ogólnej funkcji do głębokiego klonowania dowolnego obiektu, sprawdź stackoverflow.com/a/13333781/560114 .
Matt Browne
1
Jest teraz sposób na natywne głębokie klonowanie .
Dan Dascalescu
1
@DanDascalescu, mimo że jest eksperymentalny, wygląda całkiem obiecująco. Dzięki za informację!
Alberto Rivera
4

Jeśli użyte metody nie działają dobrze z obiektami obejmującymi typy danych, takie jak Date , spróbuj tego

Import _

import * as _ from 'lodash';

Obiekt głęboko klonowany

myObjCopy = _.cloneDeep(myObj);
shaheer shukur
źródło
Po prostu import _ from 'lodash';wystarczy. Ale +1 za odpowiedź „nie wymyślaj koła na nowo”.
rustyx
lodash jest nadęty. Naprawdę nie ma potrzeby ściągania lodash tylko dla prostej głębokiej kopii. Wiele innych rozwiązań tutaj. To naprawdę zła odpowiedź dla twórców stron internetowych, którzy chcą zbudować szczupłą aplikację.
Jason Rice
3

jeśli nie chcesz używać json.parse (json.stringify (obiekt)), możesz utworzyć rekurencyjne kopie klucz-wartość:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

Ale najlepszym sposobem jest utworzenie klasy, która może zwrócić jej własny klon

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}
marcel
źródło
2

Kontynuując odpowiedź @marcel, stwierdziłem, że w sklonowanym obiekcie nadal brakuje niektórych funkcji. na przykład

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

gdzie na MyObject mogłem sklonować metodę MethodA, ale metodaB została wykluczona. Stało się tak, ponieważ go brakuje

enumerable: true

co oznaczało, że nie pojawił się w

for(let key in item)

Zamiast tego przeszedłem na

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

który będzie zawierał nie wyliczalne klucze.

Odkryłem również, że prototyp ( proto ) nie został sklonowany. W tym celu użyłem

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS: Frustrujące, że nie mogłem znaleźć wbudowanej funkcji do tego.

Shane Gannon
źródło
1

Możesz to również zrobić w ten sposób,

let copiedData = JSON.parse(JSON.stringify(data));
rafee_que_
źródło
-1
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

Ale Object. assign () nie tworzy głębokiego klonu

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

Aby to naprawić, powinniśmy użyć pętli klonowania, która sprawdza każdą wartość parametru user [klucz] i, jeśli jest to obiekt, również replikować jego strukturę. Nazywa się to „głębokim klonowaniem”.

Istnieje standardowy algorytm głębokiego klonowania, który obsługuje powyższy przypadek i bardziej złożone przypadki, zwany algorytmem klonowania strukturalnego . Aby nie wymyślać koła na nowo, możemy użyć jego działającej implementacji z biblioteki JavaScript, ponieważ metoda nazywa się _.cloneDeep (obj) .

Mohamed Elshahawy
źródło
-1

Wszystkie powyższe metody nie obsługują głębokiego klonowania obiektów zagnieżdżonych na n poziomach. Nie sprawdzałem jego działania na tle innych, ale jest krótki i prosty.

Pierwszy przykład poniżej przedstawia klonowanie obiektów przy użyciu klonowania Object.assigntylko do pierwszego poziomu.

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

newPerson = Object.assign({},person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

Korzystając z poniższego podejścia do obiektu głębokich klonów

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript

Saksham
źródło
JSON.parse / stringify została wymieniona jako biedny głębokim metody klonowania dla lat . Sprawdź poprzednie odpowiedzi, a także powiązane pytania. Nie jest to również nowość w ES6.
Dan Dascalescu
@DanDascalescu Wiem o tym i myślę, że używanie go do prostych obiektów nie powinno stanowić problemu. Inni również wspomnieli o tym w swoich odpowiedziach w tym samym poście, a nawet w komentarzach. Myślę, że to nie zasługuje na złą opinię.
Saksham
Dokładnie - „inni również wspomnieli” w swoich odpowiedziach o JSON.parse / stringify. Po co publikować kolejną odpowiedź z tym samym rozwiązaniem?
Dan Dascalescu