Apakah ini cara yang baik untuk mengkloning objek di ES6?

155

Googling untuk "objek klon javascript" membawa beberapa hasil yang sangat aneh, beberapa dari mereka sudah ketinggalan zaman dan ada yang terlalu kompleks, bukan semudah hanya:

let clone = {...original};

Apakah ada yang salah dengan ini?

Dmitry Fadeev
sumber
1
ini bukan ES6 legal. Tetapi jika itu bukan, ini bukan tiruan: baik sifat tiruan dan asli Anda menunjuk ke hal yang sama sekarang. Sebagai contoh, original = { a: [1,2,3] }memberi Anda klon dengan clone.amakhluk original.a. Modifikasi melalui salah satu cloneatau originalmemodifikasi hal yang sama , jadi tidak, ini buruk =)
Mike 'Pomax' Kamermans
2
@AlbertoRivera Hal ini agak valid JavaScript, di bahwa itu adalah tahap 2 proposal yang kemungkinan menjadi tambahan masa depan untuk standar JavaScript.
Frxstrem
@Frxstrem dengan pertanyaan tentang ES6, ini bukan JavaScript yang valid =)
Mike 'Pomax' Kamermans
3
Kloning dangkal atau dalam?
Felix Kling
2
Anda benar, ini bukan ES6 yang valid, ini ES9 yang valid . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Jawaban:

240

Ini bagus untuk kloning dangkal . The objek spread adalah bagian standar dari ECMAScript 2018 .

Untuk kloning yang mendalam Anda akan membutuhkan solusi yang berbeda .

const clone = {...original} untuk mengkloning dangkal

const newobj = {...original, prop: newOne} untuk secara abadi menambahkan penyangga lain ke dokumen asli dan menyimpannya sebagai objek baru.

Mark Shust di M.academy
sumber
18
Namun, bukankah ini hanya klon yang dangkal? Seperti dalam, properti tidak dikloning secara rekursif, bukan? Oleh karena itu, original.innerObject === clone.innerObject dan mengubah original.innerObject.property akan mengubah clone.innerObject.property.
milanio
18
ya, ini adalah klon yang dangkal. jika Anda ingin klon dalam, Anda harus menggunakanJSON.parse(JSON.stringify(input))
Mark Shust di M.academy
8
/! \ JSON.parse (JSON.stringify (input)) mengacaukan tanggal, tidak ditentukan, ... Ini bukan peluru perak untuk kloning! Lihat: maxpou.fr/immutability-js-without-library
Guillaume
1
Jadi, apakah hack JSON.stringify () / JSON.parse () benar-benar cara yang disarankan untuk mengkloning mendalam suatu objek di ES6? Saya tetap merekomendasikannya. Mengganggu.
Solvitieg
3
@MarkShust JSON.parse(JSON.stringify(input))tidak akan berfungsi, karena jika ada functionsatau infinitysebagai nilai, itu hanya akan menetapkan nulldi tempat mereka. Ini hanya akan berfungsi jika nilainya sederhana literalsdan tidak functions.
backslashN
65

EDIT: Ketika jawaban ini diposting, {...obj}sintaksis tidak tersedia di sebagian besar browser. Saat ini, Anda harus baik-baik saja menggunakannya (kecuali Anda perlu mendukung IE 11).

Gunakan 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 }

Namun, ini tidak akan membuat klon yang dalam. Sampai sekarang belum ada cara kloning yang dalam.

EDIT: Seperti @Mike 'Pomax' Kamermans yang disebutkan dalam komentar, Anda dapat mengkloning objek sederhana (mis. Tidak ada prototipe, fungsi atau referensi melingkar) menggunakan JSON.parse(JSON.stringify(input))

Alberto Rivera
sumber
19
Ada satu, asalkan objek Anda adalah objek yang benar literal, dan murni data, dalam hal JSON.parse(JSON.stringify(input))ini adalah klon mendalam yang tepat. Namun, saat prototipe, fungsi atau referensi melingkar sedang dimainkan, solusi itu tidak lagi berfungsi.
Mike 'Pomax' Kamermans
@ Mike'Pomax'Kamermans Itu benar. Kehilangan fungsi untuk getter dan setter sangat buruk, ...
Alberto Rivera
Jika Anda memerlukan fungsi generik untuk mengkloning objek apa pun, periksa stackoverflow.com/a/13333781/560114 .
Matt Browne
1
Sekarang ada cara untuk melakukan kloning mendalam secara asli .
Dan Dascalescu
1
@DanDascalescu meskipun bersifat eksperimental, itu terlihat cukup menjanjikan. Terimakasih atas infonya!
Alberto Rivera
4

Jika metode yang Anda gunakan tidak berfungsi dengan baik dengan objek yang melibatkan tipe data seperti Tanggal , coba ini

Impor _

import * as _ from 'lodash';

Objek klon yang dalam

myObjCopy = _.cloneDeep(myObj);
shaheer shukur
sumber
Adil import _ from 'lodash';sudah cukup. Namun +1 untuk jawaban "jangan menemukan kembali roda".
rustyx
Lodash bengkak. Benar-benar tidak perlu menarik di Lodash hanya untuk salinan sederhana. Banyak solusi lain di sini. Ini adalah jawaban yang sangat buruk bagi pengembang web yang ingin membangun aplikasi lean.
Jason Rice
3

jika Anda tidak ingin menggunakan json.parse (json.stringify (objek)), Anda dapat membuat salinan nilai kunci secara rekursif:

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

Tetapi cara terbaik adalah membuat kelas yang dapat mengembalikan klon sendiri

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
sumber
2

Mengikuti dari jawaban oleh @marcel saya menemukan beberapa fungsi masih hilang pada objek yang dikloning. misalnya

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

dimana pada MyObject saya bisa mengkloning methodA tetapi methodB tidak termasuk. Ini terjadi karena tidak ada

enumerable: true

yang berarti tidak muncul di

for(let key in item)

Sebaliknya saya beralih ke

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

yang akan menyertakan kunci yang tidak dapat dihitung.

Saya juga menemukan bahwa prototipe ( proto ) tidak dikloning. Untuk itu saya akhirnya menggunakan

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

PS: Frustasi saya tidak bisa menemukan fungsi bawaan untuk melakukan ini.

Shane Gannon
sumber
1

Anda bisa melakukannya seperti ini juga,

let copiedData = JSON.parse(JSON.stringify(data));
rafee_que_
sumber
-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.

Tapi Object.assign () tidak membuat klon yang dalam

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

Untuk memperbaikinya, kita harus menggunakan loop kloning yang memeriksa setiap nilai pengguna [kunci] dan, jika itu adalah objek, kemudian mereplikasi strukturnya juga. Itu disebut "kloning dalam".

Ada algoritma standar untuk kloning mendalam yang menangani kasus di atas dan kasus yang lebih kompleks, yang disebut algoritma kloning terstruktur . Agar tidak menemukan kembali roda, kita dapat menggunakan implementasi yang berfungsi dari perpustakaan JavaScript lodash metode ini disebut _.cloneDeep (obj) .

Mohamed Elshahawy
sumber
-1

Semua metode di atas tidak menangani kloning objek yang dalam di mana ia bersarang ke level n. Saya tidak memeriksa kinerjanya dibandingkan yang lain tetapi pendek dan sederhana.

Contoh pertama di bawah ini menunjukkan objek kloning menggunakan Object.assignklon mana saja sampai tingkat pertama.

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

Menggunakan pendekatan di bawah ini objek klon dalam

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
sumber
JSON.parse / strify telah disebutkan sebagai metode kloning dalam yang buruk selama bertahun - tahun . Silakan periksa jawaban sebelumnya, serta pertanyaan terkait. Juga, ini bukan hal baru untuk ES6.
Dan Dascalescu
@ DanDascalescu Saya tahu ini dan saya pikir itu seharusnya tidak menjadi masalah untuk menggunakannya untuk objek sederhana. Yang lain juga menyebutkan ini dalam jawaban mereka di pos yang sama dan bahkan sebagai komentar. Saya pikir itu tidak layak downvote.
Saksham
Tepat - "yang lain juga menyebutkan" JSON.parse / strify dalam jawaban mereka. Mengapa mengirim jawaban lain dengan solusi yang sama?
Dan Dascalescu