ConfigureClone kullanarak JavaScript'te derin kopyalama

Platform artık derin kopyalamaya yönelik yerleşik bir işlev olan structuredClone() ile birlikte sunulmaktadır.

Uzun süre boyunca, bir JavaScript değerinin derin bir kopyasını oluşturmak için geçici çözümlere ve kitaplıklara başvurmak zorunda kaldınız. Platform artık derin kopyalamaya yönelik yerleşik bir işlev olan structuredClone() ile birlikte sunuluyor.

Tarayıcı Desteği

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

Kaynak

Yüzeysel kopyalar

JavaScript'te bir değerin kopyalanması derin değil, neredeyse her zaman sığdır. Bu, derinlemesine iç içe yerleştirilmiş değerlerde yapılan değişikliklerin orijinalin yanı sıra kopyada da görülebileceği anlamına gelir.

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

Bir özelliği doğrudan yüzeysel kopyaya eklemek veya 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ü eklemek veya değiştirmek, kopyayı ve orijinali her iki de etkiler:

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

{...myOriginal} ifadesi, Spread Operatörü'nü kullanarak myOriginal işlevinin (enumerable) özellikleri üzerinde yinelenir. Özellik adını ve değerini kullanır, ardından bunları yeni oluşturulmuş boş bir nesneye tek tek atar. Dolayısıyla, sonuçta ortaya çıkan nesnenin şekli aynıdır, ancak özellik ve değerler listesinin kendine ait kopyası bulunur. Değerler de kopyalanır, ancak temel değerler olarak adlandırılan değerler, JavaScript değeri tarafından primitif olmayan değerlerden farklı şekilde işlenir. MDN'den alıntı yapmak için:

JavaScript'te temel (ilk 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ımsız, simge ve null.

MDN - Temel

İlkel olmayan değerler referans olarak işlenir. Yani değeri kopyalama işlemi, aslında aynı temel nesneye bir referansı kopyalamak anlamına gelir ve bu da yüzeysel kopyalama davranışına neden olur.

Derin kopyalar

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

Eskiden JavaScript'te bir değerin derin kopyasını oluşturmanın kolay veya güzel bir yolu yoktu. Birçok kişi Lodash’in cloneDeep() işlevi gibi üçüncü taraf kitaplıklardan yararlandı. Bu sorunun en yaygın çözümü muhtemelen JSON tabanlı bir saldırıydı:

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

Bu o kadar popüler bir çözümdü ki işlemi olabildiğince hızlı hale getirmek için V8, JSON.parse() ve özellikle de yukarıdaki kalıbı V8 agresif bir şekilde optimize etti. Bu hızlı olmakla birlikte bazı eksiklikler ve üç tecrübeyi beraberinde getiriyor:

  • Yinelemeli veri yapıları: Yinelemeli bir veri yapısı sağladığınızda JSON.stringify() atlanır. Bu durum, bağlı listeler veya ağaçlarla çalışırken oldukça kolay bir şekilde gerçekleşebilir.
  • Yerleşik türler: Değer Map, Set, Date, RegExp veya ArrayBuffer gibi başka JS yerleşiklerini içeriyorsa JSON.stringify() öğesini atar.
  • İşlevler: JSON.stringify(), işlevleri sessiz bir şekilde siler.

Yapılandırılmış klonlama

Platformun birkaç yerde JavaScript değerlerinin derin kopyalarını oluşturabilmesi için zaten birkaç yerde ihtiyacı vardı: Bir JS değerinin IndexedDB'de depolanması için bir dizi serileştirme işlemi gereklidir. Böylece bu değer diskte depolanabilir ve daha sonra JS değerini geri yüklemek 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" denir ve yakın zamana kadar geliştiriciler tarafından kolayca erişilebilir değildi.

Bu durum artık değişti. HTML spesifikasyonunda değişiklik, geliştiricilerin JavaScript değerlerinin derin kopyalarını kolayca oluşturabilmelerini sağlamak için tam olarak bu algoritmayı çalıştıran structuredClone() adlı bir işlev gösterilecek.

const myDeepCopy = structuredClone(myOriginal);

Hepsi bu kadar! API'de bundan bahsedeceğiz. Daha ayrıntılı bilgi edinmek istiyorsanız MDN makalesine göz atın.

Özellikler ve sınırlamalar

Yapılandırılmış klonlama, JSON.stringify() tekniğindeki birçok eksikliği (hepsi olmasa da) ele alır. 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 güçlüdür ve genellikle daha hızlıdır.

Ancak yine de dikkatinizi dağıtabilecek bazı sınırlamalara sahiptir:

  • Prototipler: Sınıf örneğiyle structuredClone() kullanırsanız dönüş olarak düz bir nesne alırsınız değerine sahip olduğundan, yapılandırılmış klonlama nesnenin prototip zincirini siler.
  • İşlevler: Nesneniz işlev içeriyorsa structuredClone() bir DataCloneError istisnası atar.
  • Klonlanamayanlar: Bazı değerler, özellikle Error ve DOM düğümleri olmak üzere klonlanabilir değildir. Google structuredClone() ürününün fırlatmasına neden olur.

Bu sınırlamalardan herhangi biri kullanım alanınızda soruna neden oluyorsa Lodash gibi kitaplıklar, kullanım alanınıza uyan veya gelmeyen diğer derin klonlama algoritmalarının özel uygulamalarını sunmaya devam eder.

Performans

Henüz yeni bir mikro karşılaştırma karşılaştırması yapmadım ancak structuredClone() kullanıma sunulmadan ö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. Aynı kalmasını bekliyorum. Yapılandırılmış klonlamaya dayanan teknikler, daha büyük nesnelerde (önemli ölçüde) daha hızlıydı. Yeni structuredClone() ürününün, diğer API'leri kötüye kullanma yükünün ortadan kalktığı ve JSON.parse() ürününden daha güçlü olduğu göz önünde bulundurulduğunda, derin kopyalar oluşturmak için varsayılan yaklaşımınız olarak kullanmanızı öneririm.

Sonuç

JS'de bir değerin derin kopyasını oluşturmanız gerekirse (bunun nedeni, sabit veri yapıları kullanmanız veya bir işlevin, orijinali etkilemeden bir nesneyi değiştirebildiğinden emin olmak istemeniz olabilir) artık geçici çözümlere veya kitaplıklara ihtiyacınız yoktur. JS ekosisteminde artık structuredClone() yer alıyor. Yaşasın.