Seriya: JavaScript Basics Dars 70

Shallow vs Deep Clone

Shallow va deep clone farqi, V8'da primitive (value) va obyekt (reference/pointer) qiymatlarning xotirada qanday saqlanishi, structuredClone() va JSON.parse/stringify usullari, qo'lda rekursiv deep clone, va hidden class nuqtai nazaridan clone xarajati.

70-dars

Shallow vs Deep Clone

Clone (nusxalash) — mavjud obyekt/massivning yangi, mustaqil nusxasini yaratish, shunday qilib asl obyektni o'zgartirish yangi nusxaga (yoki aksincha) ta'sir qilmasin.

Shallow clone (sayoz nusxa) — faqat birinchi darajadagi property'larni nusxalaydi. Agar property'ning qiymati obyekt yoki massiv bo'lsa — u nusxalanmaydi, balki o'sha ichki obyektga bo'lgan reference (havola) nusxalanadi.

Deep clone (chuqur nusxa) — barcha ichki qatlamlargacha, rekursiv ravishda, har bir obyekt/massivni to'liq mustaqil qilib nusxalaydi.


1. Muammoni ko'rish — nega bu farq muhim

const asl = {
  ism: "Ali",
  manzil: { shahar: "Toshkent" }
};

const nusxa = { ...asl }; // spread — SHALLOW clone

nusxa.ism = "Vali";
console.log(asl.ism); // "Ali" — asl o'zgarmadi, chunki `ism` — primitive (string)

nusxa.manzil.shahar = "Samarqand";
console.log(asl.manzil.shahar); // "Samarqand" !! — ASL HAM O'ZGARDI!

Nega ikkinchi holatda asl obyekt ham o'zgardi? Chunki nusxa.manzil va asl.manzil — ikkalasi ham xotiradagi bir xil obyektga ishora qiladi. Spread operator (...) faqat birinchi darajadagi kalitlarni ko'chiradi, lekin agar qiymat obyekt bo'lsa — u qiymatning o'zi emas, **reference (manzil)**i nusxalanadi.

asl.manzil  ──┐
              ├──► { shahar: "Toshkent" } (BITTA obyekt xotirada)
nusxa.manzil ─┘

2. V8 darajasida — nega bu shunday ishlaydi (Value vs Reference)

Bu xatti-harakat V8'ning (va umuman JS dvigatellarining) xotirada qiymatlarni qanday saqlashi bilan bog'liq. JS'da ikki turdagi qiymat bor:

  1. Primitive qiymatlar (number, string, boolean, null, undefined, symbol, bigint) — bular to'g'ridan-to'g'ri o'zgaruvchi "quti"sida saqlanadi (yoki kichik butun sonlar uchun V8'da SMI — Small Integer deb ataladigan optimallashtirilgan format ishlatiladi, bevosita pointer'ning o'zida kodlanadi, alohida xotira ajratilmaydi).
  2. Obyekt turlari (object, array, function) — o'zgaruvchi bu qiymatning o'zini emas, balki heapdagi (Garbage Collection mavzusida ko'rgan xotira maydoni) obyektga **pointer (ko'rsatkich)**ni saqlaydi.
Stack (o'zgaruvchilar)          Heap (obyektlar)
─────────────────────           ──────────────────
asl.ism → "Ali" (to'g'ridan)
asl.manzil → 0x001 ────────────► { shahar: "Toshkent" }
nusxa.manzil → 0x001 ───────────►  (XUDDI SHU manzil!)

{ ...asl } bajarilganda, V8 asl.manzil qiymatini o'qiydi — bu qiymat, aslida, 0x001 degan pointer. Spread operator shu pointer'ning o'zini nusxalaydi (bu — tez amal, chunki faqat bitta raqam nusxalanadi), lekin 0x001 manzilidagi obyektning o'zini nusxalamaydi. Shuning uchun ikkala o'zgaruvchi ham bitta joyga ishora qilib qolaveradi.

Bu — dizayn kamchiligi emas, balki ataylab qilingan optimallashtirish: agar har { ...obj } chaqirilganda V8 butun ichki daraxtni chuqur nusxalasa, bu juda qimmat (sekin, ko'p xotira) operatsiya bo'lardi, hatto dasturchi buni xohlamagan holatlarda ham.


3. Shallow clone usullari

const nusxa1 = { ...asl };                    // spread (ES2018)
const nusxa2 = Object.assign({}, asl);        // Object.assign
const nusxa3 = [...asl_array];                // massiv uchun spread
const nusxa4 = asl_array.slice();             // massiv uchun slice

Hammasi bir xil cheklovga ega — faqat bitta daraja nusxalanadi.


4. Deep clone usullari

1. structuredClone() — zamonaviy, native (2022, barcha yangi brauzerlarda)

const asl = {
  ism: "Ali",
  manzil: { shahar: "Toshkent" },
  sana: new Date(),
  royxat: [1, 2, { ichki: true }]
};

const chuqurNusxa = structuredClone(asl);

chuqurNusxa.manzil.shahar = "Samarqand";
console.log(asl.manzil.shahar); // "Toshkent" — asl O'ZGARMADI!

structuredClone — brauzer taqdim qiladigan native API (Web Worker'lar orasida ma'lumot uzatish uchun ishlatiladigan **"structured clone algorithm"**ning tashqi ko'rinishi — Web Worker mavzusida yana uchraydi). Bu — Date, Map, Set, RegExp, ArrayBuffer kabi murakkab turlarni ham to'g'ri nusxalay oladi, lekin funksiyalarni nusxalay olmaydi (xato tashlaydi) va Symbol'larni saqlamaydi.

2. JSON.parse(JSON.stringify(obj)) — eski, cheklangan usul

const chuqurNusxa = JSON.parse(JSON.stringify(asl));

Bu ishlaydi, lekin jiddiy cheklovlari bor: undefined, function, Symbol — yo'qolib ketadi; Date obyekti — stringga aylanib qoladi (qayta Date bo'lmaydi); Map/Set — bo'sh obyektga aylanadi; circular reference (obyekt o'z-o'zini ko'rsatsa) bo'lsa — xato tashlaydi. Shuning uchun zamonaviy kodda structuredClone() afzal.

3. Qo'lda rekursiv clone

function deepClone(qiymat) {
  if (qiymat === null || typeof qiymat !== 'object') {
    return qiymat; // primitive — to'g'ridan-to'g'ri qaytariladi
  }

  if (Array.isArray(qiymat)) {
    return qiymat.map(deepClone); // har elementni rekursiv nusxalash
  }

  const natija = {};
  for (const kalit in qiymat) {
    natija[kalit] = deepClone(qiymat[kalit]); // har property'ni rekursiv nusxalash
  }
  return natija;
}

Bu — structuredClone ichida (kontseptual jihatdan) sodir bo'ladigan jarayonni ko'rsatadi: rekursiya orqali har bir ichki qatlam alohida, yangi obyekt sifatida qayta yaratiladi, hech qanday reference umumiy qolmaydi.


5. V8 darajasida — clone qilishning xarajati

Eslang, har bir obyekt V8'da hidden class'ga ega (oldingi mavzu). Deep clone qilinganda:

const asl = { x: 1, y: { z: 2 } };
const nusxa = structuredClone(asl);

Shallow clone ({ ...asl }) — V8 uchun tez: faqat property qiymatlari (bu holatda x — primitive, y — pointer) nusxalanadi, yangi obyekt xuddi shu hidden class'dan foydalanishi mumkin (chunki property tuzilishi bir xil).

Deep clone — V8 yangi obyektlarni butunlay noldan yaratadi, har biri uchun yangi hidden class transition zanjiri (C0 → C1 → ...) ishlaydi, va bu jarayon ichki daraxtning har bir tugunida takrorlanadi. Katta, chuqur ichma-ich obyektlar uchun bu sezilarli CPU va xotira sarflaydi — shuning uchun deep clone "arzon" amal emas, uni faqat haqiqatan kerak bo'lganda ishlatish kerak.


Qachon nima kerak (amaliy xulosa)

Holat Kerakli usul Sabab
Obyektda faqat primitive qiymatlar bor Shallow ({ ...obj }) Ichki obyekt yo'q, muammo tug'ilmaydi
Faqat bitta darajani o'zgartirish kerak Shallow Chuqur nusxalash ortiqcha xarajat
Ichki obyekt/massivni ham mustaqil o'zgartirish kerak Deep (structuredClone) Asl ma'lumotni himoya qilish uchun
React/Redux'da state yangilash Odatda Shallow, immutable pattern bilan (keyingi mavzu!) Deep clone har render'da qimmat, kerak emas
Date, Map, Set bilan ishlash structuredClone, hech qachon JSON.parse/stringify emas JSON usuli bu turlarni buzadi