ConfigureClone kullanarak JavaScript'te derin kopyalama

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

Uzun bir süredir, JavaScript değerinin derin bir kopyasını oluşturmak için geçici çözümlere ve kitaplıklara başvurmanız gerekiyordu. Platform artık derin kopyalama için yerleşik bir işlev olan structuredClone() ile birlikte gönderiliyor.

Tarayıcı desteği

  • Chrome: 98.
  • Edge: 98.
  • Firefox: 94.
  • Safari: 15.4.

Kaynak

JavaScript'te bir değeri kopyalama işlemi, derin yerine neredeyse her zaman sığ olur. Bu, derin iç içe yerleştirilmiş değerlerde yapılan değişikliklerin kopyada olduğu kadar orijinalde de görüleceği anlamına gelir.

Nesne yayılma operatörünü ... kullanarak JavaScript'te sığ bir kopya oluşturmanın bir yolu:

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

const myShallowCopy = {...myOriginal};

Doğrudan sığ kopyada bir mülk eklemek veya değiştirmek yalnızca kopyayı etkiler, orijinali etkilemez:

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

Ancak, derin iç içe yerleştirilmiş bir mülk eklemek veya değiştirmek hem kopyayı hem de orijinali etkiler:

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

{...myOriginal} ifadesi, dağıtma operatörünü kullanarak myOriginal'ın (sayılabilir) mülkleri üzerinde iterasyon yapar. Özellik adını ve değerini kullanır ve bunları yeni oluşturulmuş, boş bir nesneye tek tek atar. Bu nedenle, ortaya çıkan nesne şekil olarak aynıdır ancak özellik ve değer listesinin kendi kopyasına sahiptir. Değerler de kopyalanır ancak sözde ilkel değerler, JavaScript değeri tarafından ilkel olmayan değerlerden farklı şekilde ele alınır. MDN'den alıntı yapmak için:

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

MDN — İlkel

Basit olmayan değerler referanslar olarak işlenir. Yani değeri kopyalama işlemi, aslında aynı temel nesneye ait bir referansı kopyalamaktan ibarettir ve bu da sığ kopyalama davranışına neden olur.

Derin kopyalar

Sığ kopyanın tam tersi derin kopyadır. Derin kopyalama algoritması, bir nesnenin özelliklerini de tek tek kopyalar ancak başka bir nesneye referans bulduğunda kendini yinelemeli olarak çağırır ve bu nesnenin de kopyasını oluşturur. Bu, iki kod parçasının yanlışlıkla bir nesneyi paylaşmaması ve birbirlerinin durumunu bilmeden değiştirmemesi için çok önemli olabilir.

JavaScript'te bir değerin derin kopyasını oluşturmanın kolay veya iyi bir yolu yoktu. Birçok kullanıcı, Lodash'ın cloneDeep() işlevi gibi üçüncü taraf kitaplıklarından yararlanıyordu. Bu sorunun en yaygın çözümü, JSON tabanlı bir saldırıydı:

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

Bu, o kadar popüler bir geçici çözümdü ki JSON.parse() ve özellikle yukarıdaki kalıbı mümkün olduğunca hızlı hale getirmek için V8'de agresif bir şekilde optimize edildi. Hızlı olmasına rağmen bu yöntemin bazı eksiklikleri ve tuzak noktaları vardır:

  • Yinelenen veri yapıları: JSON.stringify(), yinelenen bir veri yapısı aldığınızda hata verir. Bu durum, bağlı listeler veya ağaçlarla çalışırken oldukça kolay bir şekilde ortaya çıkabilir.
  • Yerleşik türler: Değer Map, Set, Date, RegExp veya ArrayBuffer gibi diğer yerleşik JS türlerini içeriyorsa JSON.stringify() hatası oluşur.
  • İşlevler: JSON.stringify(), işlevleri sessizce atar.

Yapılandırılmış klonlama

Platformun, birkaç yerde JavaScript değerlerinin derin kopyalarını oluşturma özelliğine zaten ihtiyacı vardı: Bir JS değerinin IndexedDB'de depolanması, diskte depolanabilmesi ve daha sonra JS değerini geri yüklemek için serileştirilebilmesi için bir tür serileştirme gerektirir. Benzer şekilde, postMessage() aracılığıyla bir WebWorker'a mesaj göndermek için bir JS değerinin bir JS alanından diğerine aktarılması gerekir. Bunun için kullanılan algoritmaya "Yapılandırılmış Klon" adı verilir ve yakın zamana kadar geliştiriciler bu algoritmaya kolayca erişemiyordu.

Bu durum artık değişti. HTML spesifikasyonu, geliştiricilerin JavaScript değerlerinin derin kopyalarını kolayca oluşturmaları için tam olarak bu algoritmayı çalıştıran structuredClone() adlı bir işlev göstermek üzere değiştirildi.

const myDeepCopy = structuredClone(myOriginal);

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

Özellikler ve sınırlamalar

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

Ancak yine de sizi hazırlıksız yakalayabilecek bazı sınırlamaları vardır:

  • Prototipler: structuredClone() işlevini bir sınıf örneğiyle kullanırsanız yapılandırılmış klonlama, nesnenin prototip zincirini atladığından döndürülen değer olarak düz bir nesne alırsınız.
  • İşlevler: Nesnenizde işlev varsa structuredClone() bir DataCloneError istisnası oluşturur.
  • Klonlanamayanlar: Bazı değerler, özellikle Error ve DOM düğümleri, yapılandırılmış olarak klonlanamaz. Bu, structuredClone()'ün atılmasına neden olur.

Bu sınırlamalardan herhangi biri kullanım alanınız için önemliyse Lodash gibi kitaplıklar, kullanım alanınıza uygun veya uygun olmayabilecek diğer derin kopyalama algoritmalarının özel uygulamalarını sunmaya devam eder.

Performans

Yeni bir mikro karşılaştırma yapmadım ancak structuredClone() yayınlanmadan önce 2018'in başlarında bir karşılaştırma yaptım. O zamanlar JSON.parse(), çok küçük nesneler için en hızlı seçenekti. Bu durumun aynı kalmasını bekliyorum. Yapılandırılmış klonlamaya dayalı teknikler, daha büyük nesneler için (önemli ölçüde) daha hızlıydı. Yeni structuredClone()'ün diğer API'leri kötüye kullanmanın getirdiği yükü taşımadığını ve JSON.parse()'ten daha güçlü olduğunu göz önünde bulundurarak, derin kopyalar oluşturmak için varsayılan yaklaşımınız olarak bunu kullanmanızı öneririz.

Sonuç

JS'de bir değerin derin kopyasını oluşturmanız gerekiyorsa (değişmez veri yapıları kullandığınızdan veya bir işlevin orijinali etkilemeden bir nesneyi değiştirebildiğinden emin olmak istediğinizden olabilir) artık geçici çözümlere veya kitaplıklara başvurmanız gerekmiyor. JS ekosisteminde artık structuredClone() var. Yaşasın.