Takip et

JavaScript’te Derin Kopyalama (Deep Cloning) Nasıl Çalışır?

JavaScript’te Derin Kopyalama (Deep Cloning) Nasıl Çalışır? JavaScript, dinamik ve esnek bir dil olmasına rağmen, nesnelerle çalışırken ko

JavaScript’te Derin Kopyalama (Deep Cloning) Nasıl Çalışır?

JavaScript, dinamik ve esnek bir dil olmasına rağmen, nesnelerle çalışırken kopyalama (cloning) kavramı, özellikle karmaşık veri yapıları söz konusu olduğunda, geliştiriciler için kafa karıştırıcı ve hata potansiyeli taşıyan bir alan olabilir. Bir nesneyi kopyalamak istediğimizde, aslında iki ana yaklaşımla karşılaşırız: yüzeysel kopyalama (shallow cloning) ve derin kopyalama (deep cloning). Bu makalede, JavaScript’te derin kopyalamanın ne anlama geldiğini, neden önemli olduğunu, farklı yöntemlerini, avantajlarını, dezavantajlarını ve karşılaşılabilecek zorlukları detaylı bir şekilde inceleyeceğiz.

Giriş: Neden Kopyalamaya İhtiyaç Duyarız?

Bir nesneyi kopyalama ihtiyacı, genellikle orijinal nesnenin verilerini değiştirmeden üzerinde işlem yapmak istediğimizde ortaya çıkar. Örneğin, bir kullanıcının profil bilgilerini içeren bir nesneniz var ve bu bilgiler üzerinde kullanıcı arayüzünde bazı değişiklikler yapmak istiyorsunuz. Eğer orijinal nesneyi doğrudan değiştirirseniz, bu değişiklikler uygulamanızın diğer kısımlarını veya sunucuya gönderilecek veriyi istenmeyen şekillerde etkileyebilir. Bu tür senaryolarda, orijinal nesnenin bağımsız bir kopyasını oluşturmak kritik önem taşır.

Ancak, JavaScript’te nesneler referans tipleridir. Bu, bir nesneyi doğrudan başka bir değişkene atadığınızda, aslında nesnenin kendisinin değil, bellekteki adresinin kopyalandığı anlamına gelir. Her iki değişken de aynı nesneyi işaret eder ve birindeki değişiklikler diğerinde de görünür olur. İşte bu noktada, gerçek bir kopya oluşturma ihtiyacı doğar ve yüzeysel ile derin kopyalama arasındaki farklar önem kazanır.

Yüzeyel Kopyalama (Shallow Cloning) Nedir?

Yüzeyel kopyalama, bir nesnenin ilk seviyesindeki özelliklerini kopyalar. Eğer bu özelliklerin değerleri ilkel tipler (sayı, string, boolean, null, undefined, symbol, bigint) ise, bu değerler doğrudan kopyalanır. Ancak, eğer bir özelliğin değeri başka bir nesne veya dizi ise, bu durumda sadece o iç nesnenin veya dizinin referansı kopyalanır, kendisi kopyalanmaz. Sonuç olarak, hem orijinal nesne hem de kopyası aynı iç nesneleri veya dizileri işaret eder.

Yüzeyel kopyalama için JavaScript’te birkaç yaygın yöntem bulunur:

1. Spread Operatörü (...)

Hem nesneler hem de diziler için kullanılan modern ve yaygın bir yöntemdir.

const originalObject = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'New York',
    zip: '10001'
  },
  hobbies: ['reading', 'hiking']
};

const shallowCopy = { ...originalObject };

console.log(shallowCopy);
// { name: 'Alice', age: 30, address: { city: 'New York', zip: '10001' }, hobbies: ['reading', 'hiking'] }

// İlk seviye bir özelliği değiştirmek
shallowCopy.name = 'Bob';
console.log(originalObject.name); // Alice (original değişmedi)

// İç içe geçmiş bir nesnenin özelliğini değiştirmek
shallowCopy.address.city = 'Los Angeles';
console.log(originalObject.address.city); // Los Angeles (original değişti!)

// Bir dizinin elemanını değiştirmek
shallowCopy.hobbies.push('swimming');
console.log(originalObject.hobbies); // ['reading', 'hiking', 'swimming'] (original değişti!)

Yukarıdaki örnekte görüldüğü gibi, name gibi ilkel bir değerin değiştirilmesi orijinali etkilemezken, address.city veya hobbies dizisi gibi referans tiplerinin değiştirilmesi orijinal nesneyi de etkiler.

2. Object.assign()

Bir veya daha fazla kaynak nesneden hedeflenen bir nesneye tüm numaralandırılabilir kendi özelliklerinin değerlerini kopyalamak için kullanılır. Genellikle boş bir nesneye kopyalama yaparak yüzeysel bir kopya oluşturulur.

const originalObject = {
  name: 'Alice',
  address: { city: 'New York' }
};

const shallowCopy = Object.assign({}, originalObject);

shallowCopy.name = 'Carol';
console.log(originalObject.name); // Alice

shallowCopy.address.city = 'Chicago';
console.log(originalObject.address.city); // Chicago (original değişti!)

Object.assign() de spread operatörü gibi yüzeysel kopyalama yapar.

3. Array.prototype.slice() ve Array.from() (Diziler İçin)

Bu yöntemler diziler için yüzeysel kopyalama sağlar.

const originalArray = [1, { a: 2 }, 3];
const shallowCopySlice = originalArray.slice();
const shallowCopyFrom = Array.from(originalArray);

shallowCopySlice[0] = 10;
console.log(originalArray[0]); // 1

shallowCopySlice[1].a = 20;
console.log(originalArray[1].a); // 20 (original değişti!)

Yüzeyel kopyalama, basit nesneler veya iç içe nesnelerle etkileşime girmeyeceğiniz durumlarda yeterli olabilir. Ancak, karmaşık ve iç içe geçmiş nesne yapılarıyla çalışırken, her seviyede bağımsızlık sağlamak için derin kopyalamaya ihtiyacımız vardır.

Derin Kopyalama (Deep Cloning) Nedir?

Derin kopyalama, bir nesnenin kendisini ve tüm iç içe geçmiş nesnelerini ve dizilerini tamamen bağımsız bir şekilde kopyalama işlemidir. Bu, orijinal nesne ile kopyası arasında hiçbir referans paylaşımının olmadığı anlamına gelir. Kopyanın herhangi bir seviyesinde yapılan bir değişiklik, orijinal nesneyi hiçbir şekilde etkilemez. Derin kopyalama, karmaşık veri yapılarıyla güvenli bir şekilde çalışmak ve istenmeyen yan etkileri önlemek için hayati öneme sahiptir.

Derin kopyalama, yüzeysel kopyalamadan daha karmaşık bir işlemdir çünkü her bir iç içe geçmiş nesnenin veya dizinin de ayrı ayrı kopyalanması gerekir. JavaScript’te yerleşik bir “derin kopyala” fonksiyonu olmaması, geliştiricilerin bu işlemi farklı yöntemlerle gerçekleştirmesini gerektirir.

Derin Kopyalama Yöntemleri

JavaScript’te derin kopyalama yapmak için birkaç farklı yaklaşım mevcuttur. Her birinin kendi avantajları ve dezavantajları vardır.

1. JSON.parse(JSON.stringify(obj)) Yöntemi

Bu yöntem, derin kopyalama için en basit ve en yaygın kullanılan hilelerden biridir. Nesneyi önce bir JSON dizesine dönüştürür (JSON.stringify), ardından bu dizeyi tekrar bir JavaScript nesnesine ayrıştırır (JSON.parse). Bu işlem, orijinal nesneden tamamen bağımsız yeni bir nesne oluşturur.

const originalObject = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'New York',
    zip: '10001'
  },
  hobbies: ['reading', 'hiking'],
  birthDate: new Date(),
  greet: function() { console.log('Hello'); },
  status: undefined,
  score: Infinity
};

const deepCopy = JSON.parse(JSON.stringify(originalObject));

console.log(deepCopy);
// Çıktı:
// {
//   name: 'Alice',
//   age: 30,
//   address: { city: 'New York', zip: '10001' },
//   hobbies: ['reading', 'hiking'],
//   birthDate: '2023-10-27T10:00:00.000Z', // Date nesnesi stringe dönüştü
//   score: null // Infinity null oldu
// }

deepCopy.address.city = 'San Francisco';
console.log(originalObject.address.city); // New York (original değişmedi!)

// Kısıtlamaları göstermek
console.log(deepCopy.greet);       // undefined (fonksiyonlar kayboldu)
console.log(deepCopy.status);      // undefined (undefined kayboldu)
console.log(originalObject.birthDate instanceof Date); // true
console.log(deepCopy.birthDate instanceof Date);       // false (artık bir string)

Avantajları:
* Basitlik: Kullanımı son derece kolay ve tek satırda derin kopya oluşturabilir.
* Hız: Küçük ve orta boyutlu nesneler için oldukça hızlıdır.

Dezavantajları:
* Veri Kaybı:
* Fonksiyonlar (function): Kopyalanmazlar, undefined olarak kaybolurlar.
* undefined değerleri: Nesne içindeki undefined değerleri tamamen kaybolur.
* Date nesneleri: Tarih nesneleri ISO 8601 formatında bir string’e dönüştürülür, orijinal Date nesnesi tipini kaybeder.
* RegExp nesneleri: Boş bir nesneye {} dönüşür.
* Map, Set nesneleri: Boş bir nesneye {} dönüşür.
* Infinity, NaN: null değerine dönüşür.
* Symbol değerleri: Tamamen kaybolur.
* Dairesel Referanslar (Circular References): Eğer nesneniz içinde birbirine referans veren dairesel yapılar varsa, JSON.stringify() bir hata (TypeError: Converting circular structure to JSON) fırlatır ve çalışmaz.
* Prototip Zinciri: Nesnelerin prototip zincirini kopyalamaz, sadece kendi numaralandırılabilir özelliklerini kopyalar.
* Getter/Setter: Getter ve setter’ları kopyalamaz, sadece onların son değerlerini kopyalar.

Bu yöntem, sadece ilkel değerler, düz nesneler ve diziler içeren basit veri yapıları için uygundur. Daha karmaşık senaryolarda kullanılması tavsiye edilmez.

2. structuredClone() Yöntemi (Yerleşik Yapısal Klonlama Algoritması)

structuredClone() global fonksiyonu, web tarayıcılarında (ve Node.js 17+ sürümlerinde) bulunan modern bir API’dir. Adından da anlaşılacağı gibi, “yapısal klonlama algoritması”nı kullanarak derin bir kopya oluşturur. Bu algoritma, JavaScript değerlerinin bir iş parçacığı veya bir Web Worker arasında nasıl aktarılacağını veya bir IndexedDB’ye nasıl depolanacağını tanımlayan aynı algoritmadır.

const originalObject = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'New York',
    zip: '10001'
  },
  hobbies: ['reading', 'hiking'],
  birthDate: new Date(),
  regExp: /abc/g,
  myMap: new Map([[1, 'one'], [2, 'two']]),
  mySet: new Set([10, 20, 30]),
  binaryData: new Uint8Array([1, 2, 3]),
  error: new Error('Something went wrong'),
  // greet: function() { console.log('Hello'); }, // structuredClone fonksiyonları kopyalamaz
  // domNode: document.createElement('div') // DOM düğümlerini kopyalamaz
};

// Dairesel referans örneği
originalObject.self = originalObject;

try {
  const deepCopy = structuredClone(originalObject);

  console.log(deepCopy);

  deepCopy.address.city = 'Seattle';
  console.log(originalObject.address.city); // New York (original değişmedi!)

  console.log(deepCopy.birthDate instanceof Date); // true (Date nesnesi korundu)
  console.log(deepCopy.regExp instanceof RegExp);   // true (RegExp nesnesi korundu)
  console.log(deepCopy.myMap instanceof Map);       // true (Map nesnesi korundu)
  console.log(deepCopy.mySet instanceof Set);       // true (Set nesnesi korundu)
  console.log(deepCopy.binaryData instanceof Uint8Array); // true (TypedArray korundu)
  console.log(deepCopy.error instanceof Error);     // true (Error nesnesi korundu)

  // Dairesel referansın doğru kopyalandığını doğrulama
  console.log(deepCopy.self === deepCopy); // true
  console.log(deepCopy.self === originalObject); // false
} catch (e) {
  console.error("structuredClone error:", e.message);
}

Avantajları:
* Dairesel Referansları Destekler: En önemli avantajlarından biri, dairesel referansları doğru bir şekilde işleyebilmesidir.
* Birçok Yerleşik Türü Kopyalar: Date, RegExp, Map, Set, ArrayBuffer, TypedArray, Blob, File, FileList, ImageData, DOMException, Error gibi birçok yerleşik nesne türünü doğru bir şekilde kopyalar.
* Performans: C++ ile uygulandığı için genellikle çok hızlıdır.
* Güvenlik: Güvenli bir ortamda çalışır ve potansiyel güvenlik açıklarını azaltır.

Dezavantajları:
* Fonksiyonları Kopyalamaz: JSON.stringify gibi, fonksiyonları kopyalamaz (genellikle bir DataCloneError hatası fırlatır veya görmezden gelir, ancak genellikle hata verir).
* DOM Düğümlerini Kopyalamaz: DOM düğümleri gibi belirli host nesnelerini kopyalamaz.
* Prototip Zinciri: Nesnelerin prototip zincirini kopyalamaz, sadece kendi numaralandırılabilir özelliklerini kopyalar.
* Getter/Setter: Getter ve setter’ları kopyalamaz, sadece onların son değerlerini kopyalar.
* Ortam Desteği: Node.js’de 17.0.0 sürümünden itibaren kullanılabilir. Daha eski tarayıcılarda veya Node.js sürümlerinde mevcut değildir (bir polyfill gerekebilir).

structuredClone() modern uygulamalar için derin kopyalama konusunda genellikle en iyi yerleşik çözümdür, özellikle dairesel referanslar ve karmaşık yerleşik nesne türleriyle çalışırken.

3. Özel Özyinelemeli Fonksiyon (Custom Recursive Function)

Daha fazla kontrol sağlamak, belirli türleri farklı şekilde ele almak veya structuredClone()‘un mevcut olmadığı ortamlarda çalışmak için kendi derin kopyalama fonksiyonunuzu yazabilirsiniz. Bu yöntem, nesnenin her seviyesini özyinelemeli olarak dolaşarak ve her bir değeri uygun şekilde kopyalayarak çalışır. Dairesel referansları ele almak için ek bir mekanizma gereklidir.

İşte dairesel referansları da işleyebilen basit bir özyinelemeli derin kopyalama fonksiyonu örneği:

function deepClone(obj, hash = new WeakMap()) {
  // 1. Temel tipler ve null/undefined doğrudan döndürülür
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 2. Özel nesne türleri için özel işleme
  // Date nesnesi
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }
  // RegExp nesnesi
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  // Map nesnesi
  if (obj instanceof Map) {
    const clonedMap = new Map();
    hash.set(obj, clonedMap); // Dairesel referanslar için orijinali ve klonu haritala
    obj.forEach((value, key) => {
      clonedMap.set(deepClone(key, hash), deepClone(value, hash));
    });
    return clonedMap;
  }
  // Set nesnesi
  if (obj instanceof Set) {
    const clonedSet = new Set();
    hash.set(obj, clonedSet); // Dairesel referanslar için orijinali ve klonu haritala
    obj.forEach(value => {
      clonedSet.add(deepClone(value, hash));
    });
    return clonedSet;
  }
  // ArrayBuffer, TypedArray, Blob, File gibi structuredClone'un desteklediği ama
  // JSON.stringify'ın desteklemediği diğer türler için de benzer özel işlemler eklenebilir.
  // Basitlik adına bu örnekte sadece Date, RegExp, Map, Set ele alınmıştır.

  // 3. Dairesel referans kontrolü
  // Eğer bu nesneyi daha önce kopyaladıysak, kopyasını döndür
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 4. Nesnenin tipine göre kopya oluşturma
  let clone;
  if (Array.isArray(obj)) {
    clone = [];
  } else {
    // Klonun prototipini korumak için Object.create kullanılır
    clone = Object.create(Object.getPrototypeOf(obj));
  }

  // 5. Orijinal nesneyi ve klonu haritala (dairesel referanslar için)
  hash.set(obj, clone);

  // 6. Nesne özelliklerini özyinelemeli olarak kopyalama
  for (let key in obj) {
    // Kendi özelliklerini ve enumerable olanları kopyala
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }

  return clone;
}

// Örnek kullanım:
const original = {
  a: 1,
  b: 'hello',
  c: true,
  d: null,
  e: undefined,
  f: new Date(),
  g: /test/i,
  h: [1, 2, { x: 10 }],
  i: {
    j: 42,
    k: {
      l: 'world'
    }
  },
  myMap: new Map([['key1', 'value1']]),
  mySet: new Set([1, 2, 3]),
  func: () => console.log('This is a function') // Fonksiyonlar genellikle kopyalanmaz, referans olarak kalır veya ignore edilir.
};

// Dairesel referans ekleyelim
original.i.k.parent = original;
original.selfRef = original;

const cloned = deepClone(original);

console.log(cloned);

// Testler
cloned.a = 100;
cloned.i.k.l = 'universe';
cloned.h[2].x = 200;
cloned.f.setFullYear(2000);
cloned.myMap.set('key2', 'value2');
cloned.mySet.add(4);

console.log('Original a:', original.a); // 1
console.log('Cloned a:', cloned.a);     // 100

console.log('Original i.k.l:', original.i.k.l); // world
console.log('Cloned i.k.l:', cloned.i.k.l);     // universe

console.log('Original h[2].x:', original.h[2].x); // 10
console.log('Cloned h[2].x:', cloned.h[2].x);     // 200

console.log('Original date year:', original.f.getFullYear()); // Orijinal yıl
console.log('Cloned date year:', cloned.f.getFullYear());     // 2000

console.log('Original map size:', original.myMap.size); // 1
console.log('Cloned map size:', cloned.myMap.size);     // 2

console.log('Original set has 4:', original.mySet.has(4)); // false
console.log('Cloned set has 4:', cloned.mySet.has(4));     // true

console.log('Original function:', original.func); // Function
console.log('Cloned function:', cloned.func);     // Function (referans kopyalandı)

// Dairesel referans kontrolü
console.log('Cloned i.k.parent === cloned:', cloned.i.k.parent === cloned); // true
console.log('Cloned selfRef === cloned:', cloned.selfRef === cloned);       // true
console.log('Cloned i.k.parent === original:', cloned.i.k.parent === original); // false

Avantajları:
* Tam Kontrol: Hangi türlerin nasıl kopyalanacağı üzerinde tam kontrol sağlar.
* Dairesel Referans Desteği: Doğru uygulandığında dairesel referansları ele alabilir (genellikle bir WeakMap veya Map kullanarak zaten kopyalanmış nesneleri takip ederek).
* Prototip Koruma: Object.create(Object.getPrototypeOf(obj)) kullanarak klonun orijinal nesnenin prototip zincirini korumasını sağlayabilir.
* Esneklik: Fonksiyonları kopyalama, belirli özellikleri atlama veya özel işleme ekleme gibi özel gereksinimlere uyarlanabilir.

Dezavantajları:
* Karmaşıklık: Uygulaması daha zordur ve birçok kenar durumu (edge case) dikkate almayı gerektirir (null, undefined, Date, RegExp, Map, Set, TypedArray, Error, vs.).
* Performans: JavaScript’te çalıştığı için C++ ile uygulanan structuredClone() kadar hızlı olmayabilir, özellikle çok büyük ve derin nesneler için.
* Hata Potansiyeli: Yanlış uygulandığında dairesel referanslar nedeniyle sonsuz döngülere veya veri kaybına yol açabilir.
* Numaralandırılamayan Özellikler: for...in döngüsü sadece numaralandırılabilir (enumerable) özellikleri kopyalar. Object.getOwnPropertyNames() veya Object.getOwnPropertySymbols() kullanarak numaralandırılamayan özellikleri de ele almak daha karmaşık hale gelir.

4. Kütüphaneler (Lodash, rfdc, Immer vb.)

Gerçek dünya uygulamalarında, kendi derin kopyalama fonksiyonunuzu yazmak yerine, iyi test edilmiş ve optimize edilmiş kütüphaneleri kullanmak genellikle daha pratik ve güvenilirdir. Bu kütüphaneler, birçok kenar durumu, performans ve dairesel referanslar gibi zorlukları sizin yerinize ele alır.

* Lodash (_.cloneDeep):
* Açıklama: JavaScript ekosistemindeki en popüler yardımcı program kütüphanelerinden biridir. _.cloneDeep metodu, nesneleri ve dizileri tamamen derinlemesine kopyalamak için güçlü ve kapsamlı bir çözümdür. Dairesel referansları, çeşitli yerleşik nesne türlerini ve karmaşık senaryoları ele alır.
* Kullanım:

import cloneDeep from 'lodash/cloneDeep';

        const original = { a: 1, b: { c: 2 } };
        original.b.d = original; // Dairesel referans
        const cloned = cloneDeep(original);

        console.log(cloned.b.d === cloned); // true
        console.log(cloned.b.d === original); // false

* Avantajları: Kapsamlı, iyi test edilmiş, güvenilir, birçok kenar durumu ele alır.
* Dezavantajları: Kütüphane bağımlılığı ekler, sadece cloneDeep için tüm Lodash’i çekmek biraz ağır olabilir (ancak modüler import mümkündür).

* rfdc (really fast deep clone):
* Açıklama: Adından da anlaşılacağı gibi, performansa odaklanmış, çok küçük ve hızlı bir derin kopyalama kütüphanesidir. Özellikle JSON.parse(JSON.stringify()) yönteminin kısıtlamalarına takılan ancak structuredClone()‘un henüz olmadığı veya daha hafif bir çözüme ihtiyaç duyulan durumlar için tasarlanmıştır. Dairesel referansları da destekler.
* Kullanım:

import rfdc from 'rfdc';
        const clone = rfdc();

        const original = { a: 1, b: { c: 2 } };
        original.b.d = original;
        const cloned = clone(original);

        console.log(cloned.b.d === cloned); // true

* Avantajları: Çok hızlı, küçük boyutlu, dairesel referansları destekler.
* Dezavantajları: Lodash kadar kapsamlı değildir, bazı çok özel nesne türlerini (örneğin, özel sınıf örnekleri) tam olarak kopyalamayabilir.

* Immer:
* Açıklama: Immer, doğrudan bir “derin kopyalama” kütüphanesi olmaktan ziyade, immutable (değişmez) veri yapılarıyla çalışmayı kolaylaştıran bir kütüphanedir. Bir “draft” (taslak) nesnesi üzerinde doğrudan değişiklik yapmanıza izin verir ve bu değişiklikleri uyguladıktan sonra, orijinal nesneyi değiştirmeden, yalnızca değişen kısımlarını kopyalayarak yeni, immutable bir durum döndürür. Bu, dahili olarak yapısal paylaşım (structural sharing) kullanarak performansı optimize eder.
* Kullanım:

import { produce } from 'immer';

        const baseState = {
          name: 'Alice',
          address: {
            city: 'New York'
            // ...
          }
        };

        const nextState = produce(baseState, draft => {
          draft.address.city = 'London';
        });

        console.log(baseState.address.city); // New York
        console.log(nextState.address.city); // London
        console.log(baseState.address === nextState.address); // false (sadece değişen kısım kopyalandı)

* Avantajları: Immutable state yönetimini basitleştirir, performansı optimize eder, yan etkileri engeller.
* Dezavantajları: Doğrudan “klonlama” değil, “yeni bir durum oluşturma” paradigmalarını takip eder. Tüm derin kopyalama ihtiyaçları için uygun olmayabilir.

Derin Kopyalama Zorlukları ve Dikkat Edilmesi Gerekenler

Derin kopyalama, göründüğünden daha karmaşık bir konudur ve birçok potansiyel tuzak içerir:

* Dairesel Referanslar: Bir nesnenin kendi kendine veya başka bir nesne aracılığıyla tekrar kendisine referans vermesi durumudur. Bu, özyinelemeli bir kopyalama fonksiyonunun sonsuz döngüye girmesine neden olabilir. WeakMap veya Map kullanarak daha önce kopyalanmış nesneleri takip etmek bu sorunu çözer.
* Performans: Çok büyük ve derin iç içe geçmiş nesnelerin derin kopyalanması zaman alıcı olabilir. Özellikle mobil cihazlarda veya performans kritik uygulamalarda bu bir darboğaz haline gelebilir.
* Özel Nesne Türleri: Date, RegExp, Map, Set, Blob, File, Error, ArrayBuffer, TypedArray gibi yerleşik nesneler ve özel sınıf örnekleri veya DOM düğümleri gibi host nesneleri standart kopyalama yöntemleriyle doğru şekilde kopyalanmayabilir. Her bir tür için özel işlem gerekebilir.
* Fonksiyonlar: Genellikle bir nesne içindeki fonksiyonlar derin kopyalanmaz. Ya referansları kopyalanır ya da tamamen göz ardı edilir. Çoğu senaryoda, fonksiyonların kopyalanmasına gerek yoktur, çünkü genellikle durum (state) değil, davranış (behavior) temsil ederler.
* Prototip Zinciri: Varsayılan derin kopyalama yöntemleri genellikle nesnenin prototip zincirini kopyalamaz, sadece kendi özelliklerini kopyalar. Eğer prototip zincirini korumak önemliyse, bu özel olarak ele alınmalıdır (örneğin Object.create(Object.getPrototypeOf(obj))).
* Getter/Setter ve Özellik Tanımlayıcıları: Object.defineProperty ile tanımlanmış getter’lar, setter’lar ve diğer özellik tanımlayıcıları (writable, enumerable, configurable) genellikle basit derin kopyalama yöntemleriyle kaybolur ve sadece değerleri kopyalanır. Bunları korumak için Object.getOwnPropertyDescriptors ve Object.defineProperties gibi API’ler kullanılmalıdır, bu da işlemi daha karmaşık hale getirir.
* Numaralandırılamayan (Non-enumerable) Özellikler: Varsayılan olarak, for...in döngüleri veya JSON.stringify gibi yöntemler sadece numaralandırılabilir özellikleri kopyalar. Numaralandırılamayan özellikleri (örneğin, bazı yerleşik nesnelerin özellikleri) kopyalamak için Object.getOwnPropertyNames() veya Object.getOwnPropertySymbols() gibi yöntemler kullanılmalıdır.

Hangi Yöntemi Ne Zaman Kullanmalı?

* JSON.parse(JSON.stringify(obj)): En basit derin kopyalama ihtiyacınız olduğunda ve nesnenizin içinde fonksiyonlar, Date nesneleri, Map, Set, undefined gibi özel türler veya dairesel referanslar olmadığından eminseniz kullanın. Hızlı bir “hack” olarak idealdir.
* structuredClone(): Modern tarayıcılarda veya Node.js 17+ ortamlarında çalışıyorsanız ve nesnenizin içinde dairesel referanslar, Date, RegExp, Map, Set, ArrayBuffer gibi yerleşik karmaşık türler varsa, bu en iyi yerleşik çözümdür. Fonksiyonlar veya DOM düğümleri gibi desteklenmeyen türleriniz olmadığından emin olun.
* Özel Özyinelemeli Fonksiyon: structuredClone()‘un desteklenmediği eski ortamlar için, kopyalama süreci üzerinde tam kontrol sahibi olmak istediğinizde veya çok özel nesne türlerini/davranışlarını ele almanız gerektiğinde kendi fonksiyonunuzu yazın. Dairesel referansları ve tüm kenar durumlarını dikkatlice ele aldığınızdan emin olun.
* Kütüphaneler (Lodash, rfdc): Üretim seviyesi uygulamalar için, güvenilirlik, sağlamlık, performans ve geniş kapsamlı kenar durum ele alma yeteneği arıyorsanız kütüphaneler en iyi seçenektir. Genellikle kendi çözümünüzü yazmaktan daha az hata yapma potansiyeli taşır.
* Immer: Eğer birincil hedefiniz immutable veri yapılarıyla çalışmak ve state yönetimini basitleştirmekse, derin kopyalama yerine Immer gibi kütüphaneleri düşünün.

Sonuç

JavaScript’te derin kopyalama, karmaşık nesne yapılarıyla çalışırken veri bütünlüğünü sağlamak ve istenmeyen yan etkilerden kaçınmak için temel bir kavramdır. Yüzeysel kopyalama, sadece ilk seviyedeki ilkel değerleri kopyalarken, iç içe geçmiş referans tiplerini paylaşmaya devam eder. Derin kopyalama ise tüm nesne hiyerarşisini tamamen bağımsız bir şekilde yeniden oluşturur.

Her bir derin kopyalama yönteminin kendine özgü avantajları ve dezavantajları vardır. JSON.parse(JSON.stringify()) basit durumlar için hızlı bir çözüm sunarken, structuredClone() modern ve güvenilir bir yerleşik seçenektir. Daha fazla kontrol veya eski ortamlar için özel özyinelemeli fonksiyonlar yazılabilir, ancak bu daha fazla çaba ve dikkat gerektirir. Son olarak, Lodash veya rfdc gibi kütüphaneler, çoğu gerçek dünya senaryosu için test edilmiş, optimize edilmiş ve kapsamlı çözümler sunar.

Hangi yöntemi seçeceğiniz, projenizin gereksinimlerine, hedef ortamınıza, nesnenizin karmaşıklığına ve performans beklentilerinize bağlı olacaktır. Derin kopyalamanın inceliklerini anlamak, daha sağlam, hatasız ve bakımı kolay JavaScript uygulamaları geliştirmenin anahtarıdır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık