Bagaimana Anda mengkloning objek Javascript secara mendalam?
Saya tahu ada berbagai fungsi berdasarkan kerangka kerja seperti JSON.parse(JSON.stringify(o))
dan $.extend(true, {}, o)
tetapi saya tidak ingin menggunakan kerangka seperti itu.
Apa cara paling elegan atau efisien untuk membuat klon yang dalam.
Kami sangat peduli dengan kasus tepi seperti kloning array. Tidak merusak rantai prototipe, berurusan dengan referensi diri.
Kami tidak peduli tentang mendukung penyalinan objek DOM dan sejenisnya karena .cloneNode
ada karena alasan itu.
Karena saya terutama ingin menggunakan klon dalam node.js
menggunakan fitur ES5 dari mesin V8 dapat diterima.
[Sunting]
Sebelum ada yang menyarankan izinkan saya menyebutkan ada perbedaan yang jelas antara membuat salinan dengan secara prototipikal mewarisi dari objek dan mengkloningnya . Yang pertama membuat rantai prototipe menjadi berantakan.
[Edit Lebih Lanjut]
Setelah membaca jawaban Anda, saya mendapatkan penemuan menjengkelkan bahwa mengkloning seluruh objek adalah permainan yang sangat berbahaya dan sulit. Ambil contoh objek berbasis closure berikut
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
Apakah ada cara untuk menulis fungsi kloning yang mengkloning objek, memiliki status yang sama pada saat kloning tetapi tidak dapat mengubah status o
tanpa menulis parser JS di JS.
Seharusnya tidak ada kebutuhan dunia nyata untuk fungsi seperti itu lagi. Ini hanya kepentingan akademis.
sumber
clone
fungsi generik . Objek yang dipermasalahkan harus memperlihatkanclone
metode yang dibuat khusus sendiri .Jawaban:
Itu sangat tergantung pada apa yang ingin Anda klon. Apakah ini benar-benar objek JSON atau sembarang objek di JavaScript? Jika Anda ingin melakukan klon apa pun, itu mungkin membuat Anda mendapat masalah. Masalah yang mana? Saya akan menjelaskannya di bawah ini, tetapi pertama-tama, contoh kode yang mengkloning literal objek, primitif, array, dan node DOM.
Dan sekarang, mari kita bicara tentang masalah yang mungkin Anda dapatkan saat mulai mengkloning objek REAL. Saya sekarang berbicara tentang objek yang Anda buat dengan melakukan sesuatu seperti
Tentu saja Anda dapat mengkloningnya, itu bukan masalah, setiap objek mengekspos properti konstruktor, dan Anda dapat menggunakannya untuk mengkloning objek, tetapi itu tidak akan selalu berfungsi. Anda juga dapat melakukan sederhana
for in
pada objek ini, tetapi itu mengarah ke arah yang sama - masalah. Saya juga menyertakan fungsionalitas klon di dalam kode, tetapi dikecualikan olehif( false )
pernyataan.Jadi, mengapa kloning bisa menyebalkan? Pertama-tama, setiap objek / instance mungkin memiliki beberapa status. Anda tidak pernah bisa yakin bahwa objek Anda tidak memiliki, misalnya variabel privat, dan jika demikian halnya, dengan mengkloning objek, Anda hanya memutus status.
Bayangkan tidak ada negara bagian, itu bagus. Lalu kami masih punya masalah lain. Kloning melalui metode "konstruktor" akan memberi kita kendala lain. Ini adalah ketergantungan argumen. Anda tidak pernah bisa yakin, bahwa seseorang yang menciptakan objek ini, tidak melakukannya, semacam
Jika ini masalahnya, Anda kurang beruntung, someBikeInstance mungkin dibuat dalam beberapa konteks dan konteks tersebut tidak diketahui untuk metode klon.
Lalu apa yang harus dilakukan? Anda masih dapat melakukan
for in
solusi, dan memperlakukan objek seperti itu seperti objek literal normal, tetapi mungkin ide untuk tidak mengkloning objek tersebut sama sekali, dan hanya meneruskan referensi objek ini?Solusi lainnya adalah - Anda dapat menyetel konvensi bahwa semua objek yang harus digandakan harus mengimplementasikan bagian ini sendiri dan menyediakan metode API yang sesuai (seperti cloneObject). Sesuatu yang
cloneNode
dilakukan untuk DOM.Kamu putuskan.
sumber
result = new item.constructor();
menjadi buruk adalah bahwa mengingat fungsi konstruktor & objek item Anda harus dapat RE setiap parameter yang diteruskan ke konstruktor.false && item.constructor
? Bukankah itu tidakif
berguna?if
'tidak berguna' dari perspektif fungsional, karena itu tidak akan pernah berjalan, tetapi memiliki tujuan akademis untuk menunjukkan implementasi hipotetis yang mungkin coba digunakan, yang tidak disarankan oleh penulis karena alasan yang dijelaskan nanti. Jadi, ya, itu akan memicuelse
klausa setiap kali kondisi dievaluasi, namun ada alasan kode tersebut ada di sana.Cara yang sangat sederhana, mungkin terlalu sederhana:
sumber
cloneDeep
di Lodash.Solusi yang lebih baik adalah dengan menggunakan fungsi deep copy. Fungsi di bawah menyalin objek secara mendalam, dan tidak memerlukan pustaka pihak ketiga (jQuery, LoDash, dll).
sumber
var o = { a:1, b:2 } ; o["oo"] = { c:3, m:o };
bObject[k] = (v === null) ? null : (typeof v === "object") ? copy(v) : v;
Berikut adalah fungsi ES6 yang juga akan berfungsi untuk objek dengan referensi siklik:
Catatan tentang Set dan Peta
Bagaimana menangani kunci Sets dan Maps masih bisa diperdebatkan: kunci-kunci itu seringkali primitif (dalam hal ini tidak ada perdebatan), tetapi mereka juga bisa menjadi objek. Dalam hal ini pertanyaannya menjadi: haruskah kunci-kunci itu digandakan?
Orang dapat berargumen bahwa ini harus dilakukan, sehingga jika objek tersebut bermutasi dalam salinan, objek dalam aslinya tidak terpengaruh, dan sebaliknya.
Di sisi lain orang akan menginginkan bahwa jika sebuah Set / Map
has
sebuah kunci, ini harus benar baik pada aslinya maupun salinannya - setidaknya sebelum perubahan apapun dilakukan pada salah satu dari mereka. Akan aneh jika salinannya adalah Set / Map yang memiliki kunci yang tidak pernah terjadi sebelumnya (seperti yang dibuat selama proses kloning): tentunya itu tidak terlalu berguna untuk kode apa pun yang perlu mengetahui apakah objek yang diberikan adalah a kunci Set / Map itu atau tidak.Seperti yang Anda perhatikan, saya lebih berpendapat kedua: kunci Sets dan Maps adalah nilai (mungkin referensi ) yang harus tetap sama.
Pilihan seperti itu sering juga muncul dengan objek lain (mungkin khusus). Tidak ada solusi umum, karena banyak bergantung pada bagaimana objek kloning diharapkan berperilaku dalam kasus spesifik Anda.
sumber
if (object instanceof Set) Array.from(object, val => result.add(deepClone(val, hash)));
The Underscore.js contrib perpustakaan perpustakaan memiliki fungsi yang disebut snapshot bahwa klon yang mendalam objek
cuplikan dari sumber:
setelah pustaka ditautkan ke proyek Anda, panggil fungsi dengan mudah
sumber
Ini adalah metode kloning mendalam yang saya gunakan, saya pikir itu Hebat, semoga Anda memberi saran
sumber
Seperti yang telah dicatat orang lain tentang ini dan pertanyaan serupa, mengkloning "objek", dalam arti umum, meragukan di JavaScript.
Namun, ada kelas objek, yang saya sebut objek "data", yaitu, yang dibangun hanya dari
{ ... }
literal dan / atau penetapan properti sederhana atau deserialisasi dari JSON yang masuk akal untuk ingin dikloning. Baru hari ini saya ingin meningkatkan data yang diterima dari server sebesar 5x secara artifisial untuk menguji apa yang terjadi pada kumpulan data yang besar, tetapi objek (array) dan turunannya harus menjadi objek berbeda agar semuanya berfungsi dengan benar. Kloning memungkinkan saya melakukan ini untuk menggandakan kumpulan data saya:Tempat lain yang sering saya gunakan untuk mengkloning objek data adalah untuk mengirimkan data kembali ke host tempat saya ingin menghapus kolom status dari objek dalam model data sebelum mengirimkannya. Misalnya, saya mungkin ingin menghapus semua bidang yang dimulai dengan "_" dari objek saat digandakan.
Ini adalah kode yang akhirnya saya tulis untuk melakukan ini secara umum, termasuk mendukung array dan pemilih untuk memilih anggota mana yang akan dikloning (yang menggunakan string "jalur" untuk menentukan konteks):
Solusi deep-clone paling sederhana yang masuk akal, dengan asumsi objek root bukan null dan tanpa pemilihan anggota adalah:
sumber
Lo-Dash , sekarang superset dari Underscore.js , memiliki beberapa fungsi klon yang dalam:
_.cloneDeep(object)
_.cloneDeepWith(object, (val) => {if(_.isElement(val)) return val.cloneNode(true)})
parameter kedua adalah fungsi yang dipanggil untuk menghasilkan nilai kloning.
Dari jawaban penulis sendiri:
sumber
baseClone
dapat memberikan beberapa ide.Fungsi di bawah ini adalah cara paling efisien untuk mengkloning objek javascript secara mendalam.
sumber
Sebagai murni latihan, ini adalah cara yang lebih fungsional untuk melakukannya. Ini merupakan perpanjangan dari jawaban @ tfmontague seperti yang saya sarankan untuk menambahkan blok penjaga di sana. Tetapi karena saya merasa terdorong untuk menggunakan ES6 dan memfungsikan semua hal, inilah versi pimped saya. Ini memperumit logika karena Anda harus memetakan di atas array dan mengurangi objek, tetapi ini menghindari mutasi apa pun.
sumber
Saya perhatikan bahwa Map harus memerlukan perlakuan khusus, jadi dengan semua saran di utas ini, kode akan menjadi:
sumber
penambahan saya untuk semua jawaban
sumber
Ini berfungsi untuk array, objek, dan primitif. Algoritme rekursif ganda yang beralih di antara dua metode traversal:
sumber
Kita bisa memanfaatkan rekursi untuk membuat deepCopy. Itu dapat membuat salinan dari array, objek, array objek, objek dengan fungsi. Jika mau, Anda dapat menambahkan fungsi untuk jenis struktur data lain seperti peta, dll.
sumber
Gunakan immutableJS
Atau lodash / merge
sumber
Yang ini, menggunakan referensi melingkar, bekerja untuk saya
sumber
var newDate = new Date (this.oldDate); Saya melewati oldDate untuk berfungsi dan menghasilkan newDate dari this.oldDate, tetapi itu juga mengubah this.oldDate. Jadi saya menggunakan solusi itu dan berhasil.
sumber
Solusi ini akan menghindari masalah rekursi saat menggunakan [... target] atau {... target}
sumber