JavaScript: Bagaimana cara melewatkan objek dengan nilai?

96
  • Saat mengirimkan objek sebagai parameter, JavaScript meneruskannya dengan referensi dan mempersulit pembuatan salinan lokal objek.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)
    

    oakan memiliki .foodan .bar.

  • Ini mungkin untuk menyiasatinya dengan kloning; contoh sederhana:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    otidak akan memiliki .fooatau .bar.


Pertanyaan

  1. Apakah ada cara yang lebih baik untuk melewatkan objek berdasarkan nilai, selain membuat salinan / klon lokal?
vol7ron
sumber
3
Apa kasus penggunaan Anda untuk membutuhkan ini?
jondavidjohn
2
Pemrograman menyenangkan. Melihat apakah mesin JS baru telah menangani hal ini (secara teknis, ini meneruskan referensi berdasarkan nilai), tetapi terutama untuk kesenangan.
vol7ron
1
Lihat Apakah JavaScript merupakan bahasa referensi yang lewat atau lewat-demi-nilai? - JavaScript tidak melewati referensi. Seperti Java, ketika mengirimkan objek ke suatu fungsi, ia meneruskan nilai, tetapi nilainya adalah referensi. Lihat juga Apakah referensi lulus Java? .
Richard JP Le Guen
1
Sejauh yang saya tahu Anda tidak bisa melewatkan objek dengan nilai. Bahkan jika ada, itu akan berlaku melakukan kloning yang Anda maksud di atas, jadi tidak ada keuntungan yang bisa saya lihat. Selain mungkin menyimpan 3 baris kode.
Thor84no
1
@ vol7ron Ya, mereka telah menanganinya dengan menerapkan karakteristik desain bahasa dengan benar.
jondavidjohn

Jawaban:

61

Tidak juga.

Bergantung pada apa yang sebenarnya Anda butuhkan, satu kemungkinan mungkin ditetapkan osebagai prototipe objek baru.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Jadi properti apa pun yang Anda tambahkan objtidak akan ditambahkan o. Setiap properti yang ditambahkan ke objdengan nama properti yang sama dengan properti di oakan membayangi oproperti.

Tentu saja, setiap properti yang ditambahkan ke oakan tersedia objjika tidak dibayangi, dan semua objek yang ada odi rantai prototipe akan melihat pembaruan yang sama untuk o.

Selain itu, jika objmemiliki properti yang mereferensikan objek lain, seperti Array, Anda harus memastikan untuk membayangi objek tersebut sebelum menambahkan anggota ke objek, jika tidak, anggota tersebut akan ditambahkan obj, dan akan dibagikan di antara semua objek yang miliki objdalam rantai prototipe.

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Di sini Anda dapat melihat bahwa karena Anda tidak bayangan Array di bazatas odengan bazproperti pada obj, yang o.bazArray akan diubah.

Jadi, Anda harus membayangi terlebih dahulu:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined
pengguna113716
sumber
3
+1, Saya juga akan menyarankan Object.createdalam jawaban saya, tetapi saya tidak ingin menjelaskan secara berlebihan tentang kedangkalan salinan ;-)
Andy E
+1, saya lupa tentang ini. Saya sudah mencoba var obj = new Object(x). Bagi saya, saya akan berpikir itu new Object(x)akan tampil Object.create(x)secara default - menarik
vol7ron
1
@ vol7ron: Ya, new Object(x)hanya meludahkan objek yang sama (seperti yang mungkin Anda perhatikan) . Alangkah baiknya jika ada cara asli untuk mengkloning. Tidak yakin apakah ada sesuatu yang sedang dikerjakan.
pengguna113716
44

Lihat jawaban ini https://stackoverflow.com/a/5344074/746491 .

Singkatnya, JSON.parse(JSON.stringify(obj))ini adalah cara cepat untuk menyalin objek Anda, jika objek Anda dapat diserialkan ke json.

svimre
sumber
2
Saya cenderung menghindari ini karena objek tidak selalu dapat diserialkan dan karena di browser paruh baya seperti IE7 / 8, JSONTidak disertakan dan perlu didefinisikan - mungkin juga lebih deskriptif dengan nama seperti Clone. Namun, saya curiga pendapat saya dapat berubah di masa depan, karena JSON lebih terintegrasi ke dalam perangkat lunak berbasis non-browser seperti database dan IDE
vol7ron
1
Jika tidak ada fungsi dan objek tanggal, solusi JSON.parse tampaknya yang terbaik. stackoverflow.com/questions/122102/…
Herr_Hansen
Betulkah?!? Serialisasi ke JSON hanya untuk membuat salinan objek? Namun alasan lain untuk membenci bahasa yang absurd ini.
RyanNerd
1
Serialisasi JSON benar-benar cara cepat untuk menyalinnya? Menurut dokumentasi NodeJS manipulasi JSON adalah tugas CPU yang paling intensif. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad
38

Berikut adalah fungsi clone yang akan melakukan deep copy objek:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Sekarang Anda bisa menggunakan seperti ini:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)
Paul Varghese
sumber
3
Ini adalah jawaban yang dalam.
Nathan
Cantik! Jawaban terbaik IMO.
ganders
23

Menggunakan Object.assign()

Contoh:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Contoh lebih bersih yang melakukan hal yang sama:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

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

Brandon
sumber
4
Lebih baik:var b = Object.assign({}, a);
Bergi
Sepakat. Memperbarui jawaban yang sesuai. Selain itu, tidak semua browser mendukung metode assign (). Bergantung pada persyaratan kompatibilitas Anda, menggunakan polyfill seperti yang ada di MDN mungkin diperlukan.
Brandon
3
@Brandon, di link yang Anda berikan, itu memberi peringatan untuk deep cloning: "Untuk deep cloning, kita perlu menggunakan alternatif lain karena Object.assign () menyalin nilai properti. Jika nilai sumber adalah referensi ke objek, itu hanya menyalin nilai referensi itu. "
pwilcox
2
Ini berfungsi tetapi perlu diingat bahwa itu tidak melakukan salinan dalam, objek apa pun yang ada di dalamnya aakan disalin sebagai referensi.
Stoinov
15

Anda sedikit bingung tentang cara kerja objek di JavaScript. Referensi objek adalah nilai variabel. Tidak ada nilai unserialized. Saat Anda membuat objek, strukturnya disimpan dalam memori dan variabel yang ditetapkan untuk menyimpan referensi ke struktur itu.

Bahkan jika apa yang Anda tanyakan diberikan dengan cara yang mudah, konstruksi bahasa asli secara teknis masih merupakan kloning.

JavaScript benar-benar hanya nilai lewat ... hanya saja nilai yang diteruskan mungkin merujuk pada sesuatu.

Andy E
sumber
2
Hai AndyE! Saya tidak bingung dengan itu, saya berharap JS akan melakukan kloning secara default. Ada banyak ketidakefisienan dalam kloning sendiri, jadi jika mesin melakukannya, saya yakin akan lebih baik. Saya ragu kebutuhan / permintaan melebihi manfaatnya.
vol7ron
12
Di JS ketika orang mengatakan dengan referensi alih-alih dengan nilai, itu berarti nilainya seperti penunjuk dan bukan duplikat. Itu istilah yang cukup mapan.
Erik Reppen
4
@Erik: menurut pendapat saya yang sederhana, saya pikir lebih baik untuk mendefinisikannya dengan lebih jelas agar tidak membingungkan pendatang baru dalam bahasa ini. Hanya karena sesuatu sudah mapan tidak berarti itu sesuai atau bahkan secara teknis benar (karena tidak). Orang mengatakan "berdasarkan referensi" karena lebih mudah bagi mereka untuk mengatakannya daripada menjelaskan cara kerjanya.
Andy E
6
Nilainya adalah referensi ke lokasi di memori. Pada akhirnya Anda masih mengerjakan item yang sama dalam memori daripada mengerjakan salinannya. Bagaimana membuat perbedaan itu menjadi ambigu bisa membantu pendatang baru? Beginilah penjelasannya di banyak buku teknologi termasuk O'Reilly's Authoritative Guide yang sekarang ada di edisi kelima (OT: dan diperbarui dengan sangat lemah).
Erik Reppen
1
@AndyE Saya membaca lebih banyak untuk melihat apa yang diributkan. IMO, ini seperti masalah apakah JS memiliki OOP "benar" atau kita dapat menyebut sesuatu sebagai "metode" dibandingkan dengan bahasa lain. Saya tidak benar-benar yakin akan layak untuk memperlakukan vars sebagai menulis langsung ke objek yang sama dalam memori daripada menimpa pointer yang disalin dalam bahasa berbasis closure dengan percabangan yang merusak kinerja dari perilaku operator tugas. Satu-satunya perbedaan perilaku nyata dalam apa yang kami maksud di JS vs bahasa lain adalah pada apa yang terjadi saat Anda menggunakan operator penugasan.
Erik Reppen
15

Gunakan ini

x = Object.create(x1);

xdan x1akan menjadi dua objek yang berbeda, perubahan xtidak akan berubahx1

pengguna5242529
sumber
Saya akhirnya menggunakan Object.assign (). Untuk kasus uji sederhana, .create () berfungsi tetapi untuk yang lebih kompleks (yang saya belum dapat menemukan polanya), itu masih merujuk ke alamat yang sama.
Coisox
Ini tidak hanya akan berubah jika nilai dari objek tidak disimpan oleh referensi yaitu mereka adalah tipe data premitif seperti bilangan atau boolean. Misalnya, perhatikan kode di bawah ini a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (kapak);
Jjagwe Dennis
8

Javascript selalu melewati nilai. Dalam hal ini, ia meneruskan salinan referensi oke fungsi anonim. Kode menggunakan salinan referensi tetapi mengubah satu objek. Tidak ada cara untuk membuat javascript melewati apapun selain nilai.

Dalam hal ini, yang Anda inginkan adalah meneruskan salinan objek yang mendasarinya. Mengkloning objek adalah satu-satunya jalan keluar. Metode klon Anda membutuhkan sedikit pembaruan

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}
JaredPar
sumber
1
+1 Untuk "selalu melewati nilai" meskipun saya lebih suka menggunakan Panggilan dengan Berbagi Objek (sebagai lawan dari "Panggilan dengan Nilai [Referensi]") dan mempromosikan "nilai" ke "objek itu sendiri" (dengan catatan bahwa objek tidak disalin / digandakan / digandakan) karena referensi hanya merupakan detail implementasi dan tidak diekspos ke JavaScript (atau disebutkan langsung dalam spesifikasi!).
Saya mengalami masalah saat menambahkan properti ke objek yang saya terima dari koneksi jarak jauh (saya pikir itu mungkin masalah bahwa saya tidak dapat menambahkan properti apa pun ke objek itu) jadi saya mencoba fungsi shallowcopy ini dan mendapatkan kesalahan ini - Prototipe objek hanya boleh berupa Objek atau null di var copy = Object.create (o); ada ide bagaimana memecahkan masalah saya
Harshit Laddha
Apakah ini bukan salinan dalam?
River Tam
@RiverTam Tidak, ini bukan salinan dalam, karena tidak mengkloning objek di dalam objek. Namun, Anda dapat dengan mudah membuatnya menjadi salinan dalam hanya dengan membuatnya secara rekursif memanggil dirinya sendiri pada setiap sub-objek.
ABPerson
7

Sebagai pertimbangan bagi pengguna jQuery, ada juga cara untuk melakukannya dengan cara sederhana menggunakan kerangka kerja. Cara lain jQuery membuat hidup kita sedikit lebih mudah.

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

referensi :

Brett Weber
sumber
Ah - Anda menemukan pertanyaan lama ini :) Saya pikir pertanyaan aslinya lebih merupakan eksplorasi bahasa JS. Tampaknya bahkan terminologi saya lemah saat itu karena contoh saya adalah salinan dalam daripada klon . Tetapi tampaknya objek / hash hanya dapat diteruskan dengan referensi, yang umum dalam bahasa skrip
vol7ron
1
:) Datang ke sana mencari fungsi yang tepat. Saya memiliki objek data yang perlu saya salin untuk diedit sebelum mengirimkan sambil mempertahankan objek aslinya. Saya lebih suka solusi JS asli dan akan menggunakannya di pustaka dasar saya sendiri, tetapi solusi jQuery adalah perbaikan sementara saya. Jawaban yang diterima luar biasa. : D
Brett Weber
6

Sebenarnya, Javascript selalu melewati nilai . Tetapi karena referensi objek adalah nilai , objek akan berperilaku seperti diteruskan oleh referensi .

Jadi untuk berjalan di sekitar ini, string objek dan parsing kembali, keduanya menggunakan JSON. Lihat contoh kode di bawah ini:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects
Abubakar Ahmad
sumber
2
Ya, ini adalah opsi untuk konversi / konversi ulang sederhana. Umumnya, bekerja dengan string sangat tidak efisien; meskipun, saya belum mengevaluasi kinerja penguraian JSON dalam beberapa waktu. Perlu diingat bahwa meskipun ini berfungsi untuk objek sederhana, objek yang berisi fungsi sebagai nilai, akan kehilangan konten.
vol7ron
4

use obj2 = {... obj1} Sekarang kedua objek memiliki nilai yang sama dengan referensi yang berbeda

Geetanshu Gulati
sumber
2
Baru mengenal javascript di sini. Operator menyebar menciptakan nilai baru daripada menyalin dengan referensi? Jadi mengapa ini tidak disukai?
jo3birdtalk
2
Pada pandangan pertama, itu berhasil bagi saya. Tetapi saya menemukan itu hanya untuk lapisan pertama dari variabel. Misalnya, itu berhasil kapan let a = {value: "old"} let b = [...a] b.value = "new", dan a akan {value: "old"}, b akan {value: "new"}. Tapi tidak berhasil kapan let a = [{value: "old"}] let b = [...a] b[0].value = "new", dan a akan [{value: "new"}], b akan [{value: "new"}].
Brady Huang
ya itu salinan dangkal. Jika Anda memiliki objek bersarang, Anda perlu melakukan deep copy. Untuk kesederhanaan, Anda dapat menggunakan fungsi clone dan deepClone dari lodash
Geetanshu Gulati
3

Saya perlu menyalin objek berdasarkan nilai (bukan referensi) dan menurut saya halaman ini berguna:

Apa cara paling efisien untuk mengkloning objek dalam JavaScript? . Secara khusus, mengkloning objek dengan kode berikut oleh John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Bip Bop
sumber
Pertanyaannya adalah tentang JavaScript, bukan pustaka jQuery.
j08691
1
Benar, untuk orang yang tidak dapat menggunakan Jquery untuk alasan apapun ini tidak akan membantu mereka. Namun, karena Jquery adalah pustaka javascript, saya pikir itu masih akan membantu sebagian besar pengguna javascript.
BeepBop
Itu tidak akan membantu programmer mana pun yang mencoba melakukannya di server. Jadi - tidak. Ini bukan jawaban yang valid.
Tomasz Gałkowski
3

Dengan sintaks ES6:

let obj = Object.assign({}, o);

Tomasz Gałkowski
sumber
3
Sangat bagus menyebutkan @galkowskit. Masalah terbesar (lebih baik: hal yang perlu diperhatikan ) dengan the Object.assignadalah tidak melakukan deep copy.
vol7ron
1

Ketika Anda menyimpulkannya, itu hanya proxy yang terlalu rumit, tapi mungkin Catch-All Proxies bisa melakukannya?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}
Richard JP Le Guen
sumber
Richard, saya agak terlambat untuk menanggapi :) tapi saya rasa saya tidak pernah melakukannya karena beberapa alasan: Saya mencari di gula sintaksis yang memberi JS kemampuan untuk meneruskan objek dengan cara yang berbeda; jawaban Anda tampak panjang, dan saya terpaku pada penggunaan "proxy" Anda. 3 tahun kemudian dan saya masih menutup telepon untuk menggunakannya. Mungkin saya kurang memperhatikan di kelas, tapi seperti jaringan, saya pikir proxy di CS seharusnya bertindak sebagai perantara. Mungkin itu ada di sini juga dan saya tidak memikirkan dengan benar tentang apa itu perantara.
vol7ron
0

Jika Anda menggunakan lodash atau npm, gunakan fungsi gabungan lodash untuk menyalin semua properti objek ke objek kosong baru seperti:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge

Con Antonakos
sumber
Salinan dalam juga harus mencakup peristiwa, saya tidak terlalu akrab dengan lodash, apakah mencerminkan peristiwa ke objek baru?
vol7ron