JavaScript: Nesneyi değere göre nasıl geçirebilirim?


96
  • Nesneleri parametre olarak iletirken, JavaScript bunları referans olarak iletir ve nesnelerin yerel kopyalarını oluşturmayı zorlaştırır.

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

    oolacak .foove .bar.

  • Bunu klonlayarak aşmak mümkündür; basit örnek:

    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)
    

    o.fooveya olmayacak .bar.


Soru

  1. Yerel bir kopya / klon oluşturmaktan başka, nesneleri değere göre geçirmenin daha iyi bir yolu var mı?

3
Buna ihtiyaç duymak için kullanım durumunuz nedir?
jondavidjohn

2
Programlama eğlencesi. Yeni JS motorlarının bunu ele alıp almadığını görmek (teknik olarak referansı değere göre geçirmektir), ancak esas olarak eğlence için.
vol7ron

1
Bkz. JavaScript, referansla geçiş dili mi yoksa değerle aktarım dili mi? - JavaScript referans olarak geçmez. Java gibi, nesneleri bir işleve aktarırken, değere göre geçer, ancak değer bir referanstır. Ayrıca bkz. Java referansla geçiyor mu? .
Richard JP Le Guen

1
Bildiğim kadarıyla nesneleri değere göre geçiremezsiniz. Olsa bile, aslında yukarıda bahsettiğiniz klonlamayı yapıyor olacaktı, bu yüzden görebildiğim hiçbir kazanım yok. Belki 3 satır kod kaydetmekten başka.
Thor84no

1
@ vol7ron Evet, bir dil tasarım özelliğini doğru bir şekilde uygulayarak bunu ele aldılar.
jondavidjohn

Yanıtlar:


61

Pek sayılmaz.

Gerçekte neye ihtiyacınız olduğuna bağlı olarak, yeni bir nesnenin prototipini ayarlamak bir olasılık olabilir o.

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

alert( o.foo ); // undefined

Yani eklediğiniz hiçbir mülk objeklenmeyecektir o. Bir mülk objolarak aynı özellik adına eklenen tüm özellikler o, oözelliği gölgeleyecektir.

Elbette, eklenen tüm özellikler, gölgelenmemişlerse okullanılabilir olacak ve prototip zincirinde bulunan objtüm nesneler oiçin aynı güncellemeleri görecektir o.

Ayrıca, objbir Dizi gibi başka bir nesneye başvuran bir özelliğe sahipse , nesneye üye eklemeden önce o nesneyi gölgelediğinizden emin olmanız gerekir, aksi takdirde bu üyeler eklenir objve tüm nesneler arasında paylaşılır. sahip objprototip zincirinde.

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

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

})(o);

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

Burada en Diziyi gölge yoktu çünkü görebilirsiniz bazüzerinde obir ile bazilgili mülkiyet obj, o.bazDizi modifiye olur.

Bunun yerine, önce gölgelendirmeniz gerekir:

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

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

})(o);

alert( o.baz[0] );  // undefined

3
+1, cevabımda da önerecektim Object.create, ancak kopyanın sığlığını açıklamak istemedim ;-)
Andy E,

+1, bunu unuttum. Denedim var obj = new Object(x). Bana göre, bunun varsayılan olarak new Object(x)performans göstereceğini düşünürdüm Object.create(x)- ilginç
vol7ron

1
@ vol7ron: Evet, new Object(x)aynı nesneyi geri çeviriyor (muhtemelen fark ettiğiniz gibi) . Klonlamanın yerel bir yolu olsaydı güzel olurdu. İşlerde bir şey olup olmadığından emin değilim.
user113716

45

Bu cevaba göz atın https://stackoverflow.com/a/5344074/746491 .

Kısacası, JSON.parse(JSON.stringify(obj))nesneleriniz json'a serileştirilebilirse, nesnelerinizi kopyalamanın hızlı bir yoludur.


2
Nesneler her zaman serileştirilemediğinden ve IE7 / 8 gibi orta yaşlı tarayıcılarda JSONdahil edilmediğinden ve tanımlanması gerektiğinden bundan kaçınma eğilimindeyim - daha iyi bir adla daha açıklayıcı olabilir Clone. Ancak, JSON, veritabanları ve
IDE'ler

1
İşlev ve tarih nesneleri yoksa, JSON.parse çözümü en iyisi gibi görünüyor. stackoverflow.com/questions/122102/…
Herr_Hansen

Gerçekten mi?!? Yalnızca bir nesne kopyası oluşturmak için JSON'a seri hale getirilsin mi? Bu saçma dilden nefret etmek için bir başka neden daha.
RyanNerd

1
JSON'u seri hale getirmek gerçekten kopyalamanın hızlı bir yolu mu? NodeJS belgelerine göre JSON manipülasyonu, CPU'nun en yoğun olarak kullanıldığı görevdir. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad

39

Nesnenin derin kopyasını gerçekleştirecek klon işlevi:

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

Şimdi şu şekilde kullanabilirsiniz:

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

3
Bu derin bir cevaptı.
Nathan

Güzel! En iyi cevap IMO.
ganders

23

Kullanım Object.assign()

Misal:

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

Aynı şeyi yapan daha net bir örnek:

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


4
Daha iyi:var b = Object.assign({}, a);
Bergi

Kabul. Cevap buna göre güncelleniyor. Ayrıca, her tarayıcı assign () yöntemini desteklemez. Uyumluluk gereksinimlerinize bağlı olarak, MDN'deki gibi bir çoklu dolgu kullanmak gerekebilir.
Brandon

3
@Brandon, sağladığınız bağlantıda derin klonlama için bir uyarı veriyor: "Derin klonlama için başka alternatifler kullanmamız gerekiyor çünkü Object.assign () özellik değerlerini kopyalıyor. Kaynak değeri bir nesneye referans ise, yalnızca bu referans değerini kopyalar. "
pwilcox

2
Bu işe yarıyor, ancak derin kopyalama yapmadığını unutmayın, içindeki nesneler areferans olarak kopyalanacaktır.
Stoinov

16

JavaScript'te nesnelerin nasıl çalıştığı konusunda biraz kafanız karıştı. Nesnenin referansı , değişkenin değeridir. Serileştirilmemiş değer yoktur. Bir nesne oluşturduğunuzda, yapısı bellekte saklanır ve atandığı değişken bu yapıya bir referans tutar.

İstediğiniz şey bir tür kolay, ana dil yapısında sağlanmış olsa bile, teknik olarak yine de klonlama olacaktır.

JavaScript gerçekten sadece geçiş-değerdir ... sadece geçirilen değer bir şeye referans olabilir.


2
Hey AndyE! Kafam karışmadı, JS'nin klonlamayı varsayılan olarak yapacağını umuyordum. Kendi kendini klonlamada büyük verimsizlikler var, bu yüzden motor bunu yaparsa daha iyi olacağına eminim. Gerçi ihtiyacın / talebin faydayı aştığından şüpheliyim.
vol7ron

13
JS'de insanlar değere göre yerine referansa göre söylediklerinde, değerin bir kopya yerine bir işaretçi gibi olduğu anlamına gelir. Bu oldukça sağlam bir dildir.
Erik Reppen

4
@Erik: Alçakgönüllü görüşüme göre, yeni gelenlerin dille karıştırılmaması için onu daha net tanımlamak daha iyi. Bir şeyin yerleşik olması, uygun olduğu veya hatta teknik olarak doğru olduğu anlamına gelmez (çünkü değildir). İnsanlar "referans olarak" diyorlar çünkü bunu söylemek onlar için gerçekte nasıl çalıştığını açıklamaktan daha kolay.
Andy E

6
Değer, bellekteki bir konuma referanstır. Nihayetinde, bir kopya ile çalışmak yerine, hafızadaki aynı öğe üzerinde hareket ediyorsunuz. Bu ayrımı belirsiz hale getirmenin yeni gelenlere yardım etmesi nasıl mümkün? O'Reilly'nin şu anda beşinci (OT: ve çok zayıf bir şekilde güncellenmiş) baskısı olan Authoritative Guide da dahil olmak üzere birçok teknoloji kitabında bu şekilde açıklanmaktadır.
Erik Reppen

1
@AndyE Neyin yayıldığını görmek için biraz daha okudum. IMO, bu, JS'nin "gerçek" OOP'ye sahip olup olmadığı veya diğer dillere kıyasla uygun bir şekilde bir "yöntem" diyebileceğimiz meselesi gibi. Atama operatörü davranışının performans-öldürme dallanmasıyla, kopyalanan işaretçisinin üzerine kapanış tabanlı bir dilde yazmak yerine, değişkenleri doğrudan bellekteki aynı nesneye yazıyormuş gibi ele almanın mümkün olacağından aslında emin değilim. JS ile diğer dillerde kastettiğimiz şeydeki tek gerçek davranış ayrımı, atama operatörünü kullandığınızda olanlardır.
Erik Reppen

16

Bunu kullan

x = Object.create(x1);

xve x1iki farklı nesne xolacak , değişim değişmeyecekx1


Object.assign () 'ı kullanıyorum. Basit bir test senaryosu için .create () çalışıyor ancak daha karmaşık için (henüz kalıbı bulamıyorum), yine de aynı adrese başvuruyor.
Coisox

1
Yalnızca nesnenin değerleri referans olarak saklanmazsa değişmez, yani sayı veya boole gibi öncelikli veri türleri. Örneğin, a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (ax);
Jjagwe Dennis

8

Javascript her zaman değere göre geçer. Bu durumda, referansın bir kopyasını oanonim işleve aktarır. Kod, referansın bir kopyasını kullanıyor, ancak tek nesneyi değiştiriyor. Javascript'i değerden başka bir şeyle geçirmenin bir yolu yoktur.

Bu durumda istediğiniz, temeldeki nesnenin bir kopyasını iletmektir. Nesneyi klonlamak tek başvuru yoludur. Yine de klon yönteminizin biraz güncellemeye ihtiyacı var

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1 "Her zaman değere göre geçer" için Nesne Paylaşımı ile Çağrıyı kullanmayı tercih etsem de ("Referansın Değerine Göre Çağırma" yerine) ve "değeri" "nesnenin kendisine" (şu notla birlikte) referanslar yalnızca bir uygulama ayrıntısı olduğundan ve JavaScript'e açık olmadığından (veya spesifikasyonda doğrudan bahsedilmediğinden!) nesne kopyalanmaz / klonlanmaz / çoğaltılmaz).

Uzak bir bağlantıdan aldığım bir nesneye özellik eklerken sorun yaşadım (bu nesneye herhangi bir özellik ekleyemememin sorun olabileceğini düşündüm) bu yüzden bu sığ kopya işlevini denedim ve bu hatayı aldım - Nesne prototipi var copy = Object.create (o) konumunda yalnızca bir Object veya null olabilir; Benim sorunu çözmek için nasıl bir fikrin
Harşit Laddha

Bu derin bir kopya değil mi?
River Tam

@RiverTam Hayır, derin bir kopya değildir çünkü nesnelerin içindeki nesneleri klonlamaz. Ancak, alt nesnelerin her birinde kendisini yinelemeli olarak çağırarak kolayca derin bir kopya yapabilirsiniz.
ABPerson

7

JQuery kullanıcıları için bir değerlendirme olarak, bunu çerçeveyi kullanarak basit bir şekilde yapmanın bir yolu da vardır. JQuery'nin hayatımızı biraz daha kolaylaştırmasının başka bir yolu.

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

Referanslar :


Ah - bu eski soruyu keşfettiniz :) Bence asıl soru daha çok JS dilinin araştırılmasıydı. Benim örneğim bir klondan ziyade derin bir kopya olduğu için o zamanlar terminolojim bile zayıftı . Ama görünen o ki nesneler / karmalar yalnızca komut dosyası dillerinde yaygın olan referans ile aktarılabilir
vol7ron

1
:) Tam işlevselliği bulmak için karşısına çıktı. Orijinal nesneyi korurken göndermeden önce düzenlemek için kopyalamam gereken bir veri nesnem vardı. Yerel JS çözümünü tercih ediyorum ve bunu kendi temel kitaplığımda kullanacağım, ancak jQuery çözümü benim geçici düzeltmem. Kabul edilen cevap harika. : D
Brett Weber

6

Aslında, Javascript her zaman değere göre aktarılır . Ancak nesne referansları değerler olduğundan , nesneler referansla aktarılmış gibi davranacaktır .

Bu yüzden, bunun etrafında dolaşmak için, nesneyi dizginleyin ve her ikisi de JSON kullanarak ayrıştırın . Aşağıdaki kod örneğine bakın:

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

2
Evet, bu basit dönüştürme / yeniden dönüştürme seçeneğidir. Genellikle dizelerle çalışmak çok verimsizdir; yine de, JSON ayrıştırma performansını bir süredir değerlendirmedim. Bu basit nesneler için çalışırken, değerler olarak işlevler içeren nesnelerin içerik kaybedeceğini unutmayın.
vol7ron

Aslında bunu karşılaştırdım ve başkalarının yaptığını gördüm ve javascripts json motorundaki verimlilik nedeniyle bu yöntem gerçekten büyük bir kitaplık getirmeden, çok fazla kod yazmadan veya yapmadan bunu yapmanın en hızlı yollarından biridir. gerçek bir derin kopya.
tedivm

4

obj2 = {... obj1} kullanın Şimdi her iki nesne de aynı değere sahip ve farklı referanslara sahip


2
JavaScript'te yeni burada. Yayılma operatörü referansa göre kopyalamak yerine yeni değer yaratıyor mu? Peki bu neden reddedildi?
jo3birdtalk

2
İlk bakışta benim için çalışıyor. Ama bunun sadece değişkenin ilk katmanında olduğunu keşfettim. Örneğin, a'nın ne zaman let a = {value: "old"} let b = [...a] b.value = "new"olacağını ve {value: "old"}b'nin ne zaman olacağını hesaplar {value: "new"}. Ama ne zaman işe yaramıyor let a = [{value: "old"}] let b = [...a] b[0].value = "new"ve a olacak [{value: "new"}], b olacak [{value: "new"}].
Brady Huang

evet sığ kopya yapıyor. İç içe nesneniz varsa, derin kopyalama yapmanız gerekir. Basit olması için lodash'ın klon ve deepClone fonksiyonlarını kullanabilirsiniz
Geetanshu Gulati

3

Bir nesneyi değere göre kopyalamam gerekiyordu (referans değil) ve bu sayfayı yararlı buldum:

JavaScript'te bir nesneyi derinlemesine klonlamanın en etkili yolu nedir? . Özellikle, John Resig tarafından aşağıdaki kodla bir nesnenin klonlanması:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Soru jQuery kitaplığı değil, JavaScript hakkındadır.
j08691

1
Doğru, Jquery'yi herhangi bir nedenle kullanamayanlar için bu onlara yardımcı olmaz. Bununla birlikte, Jquery bir javascript kitaplığı olduğundan, yine de çoğu javascript kullanıcısına yardımcı olacağını düşünüyorum.
BeepBop

Bunu sunucuda yapmaya çalışan programcıların hiçbirine yardımcı olmaz. Yani - hayır. Bu geçerli bir cevap değil.
Tomasz Gałkowski

3

ES6 sözdizimi ile:

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


3
Çok güzel söz @galkowskit. İle ilgili en büyük sorun (daha iyi: dikkat edilmesi gereken şey ) Object.assignderin bir kopya yapmamasıdır.
vol7ron

1

Kısaca özetlediğinizde, bu sadece aşırı karmaşık bir proxy'dir, ancak belki Catch-All Proxy'ler bunu yapabilir mi?

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, yanıt vermek için biraz geç kaldım :) ama sanırım birkaç nedenden ötürü hiç yapmadım: JS'ye nesneleri farklı şekillerde geçirme yeteneği veren sözdizimsel şekere bakıyordum; Cevabınız uzun görünüyordu ve "vekil" kullanımınıza takılı kaldım. 3 yıl sonra ve hala kullanıma kapatıyorum. Belki sınıfta yeterince dikkat etmedim, ancak ağ oluşturma gibi CS'deki vekillerin bir aracı olarak hareket etmesi gerektiğini düşündüm. Belki de buradadır ve neyin arabuluculuğunu doğru bir şekilde düşünmüyorum.
vol7ron

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.