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 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 hem kopyada hem de orijinalde görüneceğ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

İ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 tam 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 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 sorun için muhtemelen 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:

  • 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 ortaya çıkabilir.
  • 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ş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şturması 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. 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ğ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 dikkatinizi dağıtabilecek 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ı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

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ız veya bir işlevin orijinali etkilemeden bir nesneyi değiştirebildiğinden emin olmak istediğiniz için olabilir) artık geçici çözümlere veya kitaplıklara başvurmanız gerekmiyor. JS ekosisteminde artık structuredClone() var. Yaşasın.