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:
- 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).
- 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 |