Series: JavaScript Basics Lesson 71

Immutability

Immutability falsafasi: reference tenglik orqali React/Redux'ning tez re-render qilishi, V8'da hidden class barqarorligi va structural sharing, Object.freeze() ning shallow ekani va deepFreeze, va Immer kabi kutubxonalarning Proxy asosida ishlashi.

71-dars

Immutability

Immutability (o'zgarmaslik) — ma'lumotni yaratilgandan keyin hech qachon to'g'ridan-to'g'ri o'zgartirmaslik, balki har bir "o'zgarish" kerak bo'lganda — eski ma'lumotga tegmasdan, yangi nusxa yaratish falsafasi.

Bunga qarama-qarshi — mutability (o'zgaruvchanlik): mavjud obyekt/massivni joyida (in-place) o'zgartirish.

// MUTABLE (o'zgaruvchan) — aslni to'g'ridan-to'g'ri o'zgartiradi
const foydalanuvchi = { ism: "Ali", yosh: 25 };
foydalanuvchi.yosh = 26; // ASL obyekt o'zgardi

// IMMUTABLE (o'zgarmas) — yangi obyekt yaratadi, aslga tegmaydi
const foydalanuvchi2 = { ism: "Ali", yosh: 25 };
const yangilangan = { ...foydalanuvchi2, yosh: 26 }; // YANGI obyekt
console.log(foydalanuvchi2.yosh); // 25 — asl o'zgarmadi
console.log(yangilangan.yosh);    // 26 — yangi obyektda o'zgargan

1. Nega bu kerak?

1. Bashorat qilinadigan (predictable) kod

Agar funksiyaga obyekt uzatilsa va funksiya uni mutate qilsa (o'zgartirsa), chaqiruvchi tomon kutilmagan natija oladi:

function narxniOshirish(mahsulot) {
  mahsulot.narx += 100; // YON TA'SIR — chaqiruvchiga qaytmasdan ham asl o'zgaradi
  return mahsulot;
}

const original = { nomi: "Telefon", narx: 500 };
const yangi = narxniOshirish(original);

console.log(original.narx); // 600 — SIZ BUNI KUTMAGAN BO'LISHINGIZ MUMKIN!

Bu — katta dasturlarda eng ko'p uchraydigan bug manbalaridan biri: obyekt bir necha joyda ishlatiladi, va biror funksiya uni "yon tomondan" o'zgartirib qo'yadi, boshqa qismlar esa buni bilmay qoladi.

2. Referens tenglik (reference equality) tekshiruvi tez ishlashi

const a = { x: 1 };
const b = a;
b.x = 2;

console.log(a === b); // true — ular BIR XIL obyekt, hech qachon "o'zgargan" deb bilib bo'lmaydi

Agar a mutate qilingan bo'lsa, a === b solishtirish hech narsa aytmaydi — chunki ikkalasi ham bir xil manzilga ishora qiladi. Immutable yondashuvda esa, har o'zgarishda yangi obyekt yaratilgani uchun, === orqali **"o'zgardimi yo'qmi"**ni darhol, chuqur solishtirmasdan bilib olish mumkin:

let holat = { x: 1 };
const eskiHolat = holat;

holat = { ...holat, x: 2 }; // YANGI obyekt

console.log(holat === eskiHolat); // false — bu "narsa o'zgardi" degani, TEZ tekshiruv

Bu — React, Redux kabi kutubxonalarning ishlash tezligi asosi: React shouldComponentUpdate/React.memo ichida obyektlarni chuqur solishtirmaydi (bu sekin bo'lardi), balki faqat === (reference) solishtiradi. Agar dasturchi state'ni mutate qilsa (masalan, state.x = 2 to'g'ridan-to'g'ri), React === orqali "o'zgarish yo'q" deb noto'g'ri xulosa chiqaradi va qayta render qilmaydi — bu ko'p React dasturchilar duch keladigan klassik xato.


2. V8 darajasida — nega immutability tez ishlaydi

1. Hidden class barqarorligi

Oldingi mavzuda ko'rganimizdek, obyektga yangi property qo'shish yoki turini o'zgartirish — hidden class transition'ini keltirib chiqaradi. Immutable pattern'da obyekt yaratilgandan keyin hech qachon o'zgartirilmaydi — bu degani, uning hidden class'i bir marta o'rnatiladi va hech qachon o'zgarmaydi. Bu V8'ga shu obyekt bilan ishlaydigan kodni barqaror, monomorphic holda optimallashtirish imkonini beradi.

2. Structural sharing (strukturaviy ulashish)

Immutable pattern'ning eng muhim texnik g'oyasi — har safar butun obyektni chuqur nusxalash shart emas, faqat o'zgargan qismgacha bo'lgan yo'lni nusxalash kifoya, qolgan (o'zgarmagan) qismlar eski obyekt bilan ulashiladi (share qilinadi):

const holat = {
  foydalanuvchi: { ism: "Ali" },
  sozlamalar: { tema: "dark", til: "uz" }
};

const yangiHolat = {
  ...holat,
  sozlamalar: { ...holat.sozlamalar, tema: "light" }
};

console.log(yangiHolat.foydalanuvchi === holat.foydalanuvchi); // true — QAYTA yaratilmagan!
console.log(yangiHolat.sozlamalar === holat.sozlamalar);       // false — bu o'zgargan

foydalanuvchi obyekti o'zgarmagani uchun, yangiHolat.foydalanuvchi va holat.foydalanuvchi — bitta xotiradagi obyektga ishora qiladi (pointer nusxalandi, obyektning o'zi emas). Faqat sozlamalar — o'zgargan qism — yangi obyekt sifatida yaratildi. Bu — shallow clone'ning aynan o'zi (oldingi mavzu), lekin ataylab, tizimli tarzda qo'llanilgan holati.

Bu yondashuv xotira nuqtai nazaridan juda samarali: agar deep clone qilinganida, butun daraxt qayta yaratilgan bo'lardi (yangi hidden class'lar, yangi xotira). Structural sharing esa faqat o'zgargan yo'l bo'ylab yangi obyekt yaratadi, GC uchun ham kamroq yangi obyekt — kamroq ish.


3. Object.freeze() — V8 darajasida haqiqiy o'zgarmaslikni majburlash

Yuqoridagi misollar — konventsiya (dasturchi o'zi mutate qilmaslikka rozi bo'ladi), lekin JS hech narsani majburlamaydi:

const obj = { x: 1 };
obj.x = 2; // hech qanday xato yo'q, jim-jimgina o'zgaradi!

Object.freeze() — obyektni haqiqatan o'zgarmas qiladi (runtime darajasida):

const obj = Object.freeze({ x: 1 });
obj.x = 2;              // jim o'zgarmaydi (strict mode'da XATO tashlaydi)
console.log(obj.x);     // 1 — o'zgarmadi

Object.isFrozen(obj);   // true

V8 darajasida Object.freeze() chaqirilganda, V8 o'sha obyektning hidden class'ini maxsus, "frozen" holatga o'tkazadi — bu holatda barcha property'lar writable: false, configurable: false deb belgilanadi. Bu V8'ga qo'shimcha optimallashtirish imkonini beradi: chunki dvigatel endi 100% ishonch bilan biladiki, bu obyekt hech qachon o'zgarmaydi — demak uni ishlatuvchi kod uchun eng qat'iy, eng tezkor mashina kodi yozish mumkin, hech qanday "ehtiyot" tekshiruvisiz.

Muhim cheklov: Object.freeze() — faqat sayoz (shallow)!

const obj = Object.freeze({
  ism: "Ali",
  manzil: { shahar: "Toshkent" }
});

obj.manzil.shahar = "Samarqand"; // BU ISHLAYDI! freeze faqat 1-darajani qamraydi
console.log(obj.manzil.shahar); // "Samarqand"

Chuqur (deep) freeze kerak bo'lsa, rekursiv qo'llash kerak:

function deepFreeze(obj) {
  Object.getOwnPropertyNames(obj).forEach((kalit) => {
    const qiymat = obj[kalit];
    if (qiymat && typeof qiymat === 'object') {
      deepFreeze(qiymat);
    }
  });
  return Object.freeze(obj);
}

4. Immutable ma'lumot tuzilmalari (kutubxonalar darajasida)

Katta ilovalarda, har safar { ...obj } qilish katta, chuqur obyektlar uchun samarasiz bo'lib qolishi mumkin. Shuning uchun persistent data structures (masalan, Immutable.js, yoki zamonaviyroq — Immer kutubxonasi) ishlatiladi — ular ichki tomondan trie (daraxt) tuzilmasi orqali, faqat o'zgargan "shoxcha"larni yangilab, structural sharing'ni avtomatik va samarali qiladi, dasturchi qo'lda spread yozmasdan:

import produce from 'immer';

const yangiHolat = produce(holat, (draft) => {
  draft.sozlamalar.tema = "light"; // MUTATE qilingandek yoziladi
});
// lekin ICHKI TOMONDAN Immer buni immutable, structural-sharing bilan qiladi

Immer — ichki tomondan JS Proxy (V8'ning yana bir ilg'or mexanizmi) ishlatib, "draft" obyektga qilingan har bir o'zgarishni kuzatadi va oxirida faqat o'zgargan qismlarni yangi obyekt sifatida qayta yaratadi — dasturchi esa mutable kod yozayotgandek his qiladi, lekin natija to'liq immutable bo'ladi.


Amaliy xulosa

Holat Immutability foydali
React/Redux/Vuex state boshqaruvi ✅ Har doim — reference tenglik orqali tez re-render
Funksional dasturlash uslubi ✅ Yon ta'sirlarsiz, bashorat qilinadigan kod
Konfiguratsiya obyektlari (o'zgarmasligi kerak) ✅ Object.freeze() bilan majburlash
Katta, chuqur obyektlarni tez-tez yangilash ⚠️ Ehtiyot bo'lish kerak — Immer kabi kutubxona ko'rib chiqilsin
Ishlash tezligi juda muhim, kichik lokal o'zgaruvchilar ❌ Har doim shart emas — oddiy let yetarli bo'lishi mumkin