ConfigureClone kullanarak JavaScript'te derin kopyalama

Platform artık derin kopyalama için yerleşik bir işlev olan structuredClone() ile sunuluyor.

Uzun zamandır, bir JavaScript değerinin ayrıntılı bir kopyasını oluşturmak için geçici çözümlere ve kitaplıklara başvurmak zorunda kalıyordunuz. Platform artık derin kopyalama için yerleşik bir işlev olan structuredClone() ile birlikte sunuluyor.

Tarayıcı Desteği

  • 98
  • 98
  • 94
  • 15,4

Kaynak

Yüzeysel kopyalara izin ver

JavaScript'te bir değeri kopyalamak, derin değil, hemen hemen her zaman yüzedir. Bu, derinlemesine iç içe yerleştirilmiş değerlerde yapılan değişikliklerin hem kopyada hem de orijinalde görüneceği anlamına gelir.

Nesne yayma operatörünü ... kullanarak JavaScript'te yüzeysel bir kopya oluşturmanın bir yolu:

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

Doğrudan yüzeysel kopyaya özellik eklemek veya özelliği değiştirmek yalnızca kopyayı etkiler, orijinali etkilemez:

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

Ancak, derinlemesine iç içe yerleştirilmiş bir mülkün eklenmesi veya değiştirilmesi, hem kopyayı hem de orijinali etkiler:

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

{...myOriginal} ifadesi, Yayılma Operatörü'nü kullanarak myOriginal öğesinin (enumerable) özellikleri üzerinde yinelenir. Özellik adını ve değerini kullanıp bunları yeni oluşturulmuş boş bir nesneye tek tek atar. Bu şekilde, sonuç olarak elde edilen nesnenin şekli aynıdır, ancak özellik ve değer listesinin kendi kopyası vardır. Değerler de kopyalanır, ancak temel olarak adlandırılan değerler, JavaScript değeri tarafından temel olmayan değerlerden farklı bir şekilde işlenir. MDN'den alıntı yapmak için:

JavaScript'te temel (temel değer, temel veri türü) nesne olmayan ve yöntemi olmayan verilerdir. Yedi temel veri türü vardır: dize, sayı, bigint, boole, tanımlanmamış, simge ve null.

MDN - Temel

Temel olmayan değerler references olarak işlenir. Diğer bir deyişle, değeri kopyalama işlemi aslında yalnızca aynı temel nesneye bir referansı kopyalamaktır, bu da yüzeysel kopyalama davranışına neden olur.

Derin kopyalar

Sığ kopyanın tersi, derin kopyadır. Derin kopya algoritması, bir nesnenin özelliklerini tek tek kopyalar ancak başka bir nesneye başvuru bulduğunda kendisini yinelemeli olarak çağırarak söz konusu nesnenin bir kopyasını da oluşturur. Bu, iki kod parçasının yanlışlıkla bir nesneyi paylaşmamasını ve farkında olmadan birbirlerinin durumunu etkilememesini sağlamak açısından çok önemli olabilir.

Eskiden JavaScript'te bir değerin derin kopyasını oluşturmanın kolay veya güzel bir yolu yoktu. Çoğu kullanıcı Lodash'in cloneDeep() işlevi gibi üçüncü taraf kitaplıklardan yararlanıyordu. Muhtemelen bu sorunun en yaygın çözümü JSON tabanlı bir saldırıydı:

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

Aslında, bu son derece popüler bir çözümdü. V8, JSON.parse() ve özellikle de bunu mümkün olduğunca hızlı hale getirmek için yoğun bir şekilde optimize etti. Her ne kadar hızlı olsa da birtakım eksiklikleri ve telleri var:

  • Yinelemeli veri yapıları: JSON.stringify(), ona yinelemeli bir veri yapısı sağladığınızda hata verir. Bu işlem, bağlı listeler veya ağaçlarla çalışırken oldukça kolaydır.
  • Yerleşik türler: Değer, Map, Set, Date, RegExp veya ArrayBuffer gibi başka JS yerleşiklerini içeriyorsa JSON.stringify() hatası bildirilir.
  • İşlevler: JSON.stringify(), işlevleri sessizce siler.

Yapılandırılmış klonlama

Platformun zaten birkaç yerde JavaScript değerlerinin derin kopyalarını oluşturabilmesi gerekiyordu: IndexedDB'de bir JS değerinin depolanması, bir tür serileştirme gerektirir. Böylece değer diskte depolanabilir ve daha sonra JS değerinin geri yüklenmesi için seri durumdan çıkarılabilir. Benzer şekilde, postMessage() aracılığıyla bir WebWorker'a ileti göndermek için JS değerinin bir JS alanından diğerine aktarılması gerekir. Bunun için kullanılan algoritmaya "Yapılandırılmış Klon" adı veriliyor ve geliştiriciler yakın zamana kadar bu algoritmaya kolayca erişemiyordu.

Bu durum artık değişti. HTML spesifikasyonu, geliştiricilerin JavaScript değerlerinin derin kopyalarını kolayca oluşturabilmesi için tam olarak bu algoritmayı çalıştıran structuredClone() adlı işlevi gösterecek şekilde değiştirildi.

const myDeepCopy = structuredClone(myOriginal);

Hepsi bu kadar! API'nin tamamı bundan ibarettir. Ayrıntıları daha ayrıntılı incelemek istiyorsanız MDN makalesine göz atın.

Özellikler ve sınırlamalar

Yapılandırılmış klonlama, JSON.stringify() tekniğinin birçok (tüm olmasa da) eksikliklerini giderir. Yapılandırılmış klonlama, döngüsel veri yapılarını işleyebilir, birçok yerleşik veri türünü destekler ve genellikle daha sağlamdır ve genellikle daha hızlıdır.

Ancak, yine de sizi gözden kaçırmanıza neden olabilecek bazı sınırlamalara sahiptir:

  • Prototipler: Bir sınıf örneğiyle structuredClone() kullanırsanız yapılandırılmış klonlama nesnenin prototip zincirini silerken döndürülen değer olarak düz bir nesne elde edersiniz.
  • İşlevler: Nesneniz işlevler içeriyorsa structuredClone(), bir DataCloneError istisnası atar.
  • Klonlanamaz öğeler: Bazı değerler, en önemlisi Error ve DOM düğümleri olmak üzere yapılandırılmış klonlanabilir değildir. structuredClone() oyununun fırlatmasına neden olur.

Bu sınırlamalardan herhangi biri kullanım alanınız için sorun yaratıyorsa Lodash gibi kitaplıklar, kullanım alanınıza uyan veya uymayabilecek diğer derin klonlama algoritmalarının özel uygulamalarını yine de sağlar.

Performans

Yeni bir mikro karşılaştırma karşılaştırması yapmadım ancak 2018'in başlarında, structuredClone() kullanıma sunulmadan önce bir karşılaştırma yaptım. O zamanlar JSON.parse(), çok küçük nesneler için en hızlı seçenekti. Bu oranın aynı kalmasını bekliyorum. Yapılandırılmış klonlamaya dayanan teknikler daha büyük nesneler için (önemli ölçüde) daha hızlıydı. Yeni structuredClone(), diğer API'leri kötüye kullanmanın getirdiği yükü ortadan kaldırdığını ve JSON.parse() ile karşılaştırıldığında daha sağlam olduğunu düşünerek bu sürümü, derin kopyalar oluştururken varsayılan yaklaşımınız haline getirmenizi öneririz.

Sonuç

JS'de bir değerin derin kopyasını oluşturmanız gerekiyorsa (değişmez veri yapıları kullandığınız veya bir işlevin orijinali etkilemeden bir nesneyi değiştirebildiğinden emin olmak istediğiniz için) artık geçici çözümler veya kitaplıklara erişmeniz gerekmez. JS ekosisteminde artık structuredClone() bulunmaktadır. Yaşasın.