Proxy va Reflect

~20 daq · JavaScript · Ilg'or mavzular

Muammodan boshlaylik

Ob'ektga xususiyat yozilganda avtomatik tekshirish kerak. Yoki o'qilganda — kim o'qiganini log qilish. Yoki mavjud bo'lmagan xususiyatga murojaat qilinganda xato o'rniga standart qiymat berish.

Oddiy ob'ektda buni qilib bo'lmaydi. Getter va setter — faqat oldindan nomi ma'lum xususiyatlar uchun. Dinamik, istalgan xususiyat uchun — setter yoza olmaysiz.

Proxy — ob'ektga har qanday murojaatni "ushlab olish" imkoniyatini beradi. O'qish, yozish, o'chirish — barchasini nazorat qilish.


Proxy nima?

Proxy — ob'ektni "o'rab oluvchi" qatlam. Ob'ektga har murojaat — avval Proxy orqali o'tadi. Proxy qaror qiladi: murojaatni o'tkazishmi, to'xtatishmi, o'zgartirishmi.

Hayotiy o'xshatma

Kotib (referent). Direktorga murojaat qilmoqchisiz. Lekin direktorga to'g'ridan bora olmaysiz — kotib orqali. Kotib qaror qiladi: "direktorn band, keyinroq keling" yoki "kiring", yoki "bu savol boshqa bo'limga tegishli — u yerga boring". Kotib — Proxy. Direktor — haqiqiy ob'ekt.


Proxy yaratish

const haqiqiy_ob = { ism: "Jasur", yosh: 25 };

const proksi = new Proxy(haqiqiy_ob, {
  // "trap" lar — ushlash funksiyalari
});

Ikkinchi argument — handler — trap lar to'plami. Trap — murojaat ushlanganida ishlaydigan funksiya.


get trap — o'qishni ushlab olish

Xususiyat o'qilganda ishlaydi:

const foydalanuvchi = { ism: "Jasur", yosh: 25 };

const proksi = new Proxy(foydalanuvchi, {
  get(target, xususiyat) {
    //  ↑       ↑
    // haqiqiy ob'ekt    xususiyat nomi

    console.log(`"${xususiyat}" o'qildi`);

    // Xususiyat mavjud bo'lsa — haqiqiy qiymat
    // Mavjud bo'lmasa — standart qiymat
    return xususiyat in target
      ? target[xususiyat]
      : `"${xususiyat}" mavjud emas`;
  }
});

console.log(proksi.ism);      // ← "ism" o'qildi → "Jasur"
console.log(proksi.telefon);  // ← "telefon" o'qildi → '"telefon" mavjud emas'

Nima uchun bu foydali?

Odatda mavjud bo'lmagan xususiyat undefined qaytaradi — xato ko'rinmaydi, muammo kechroq chiqadi. Proxy bilan — darhol aniq xabar.


set trap — yozishni ushlab olish

Xususiyatga qiymat berilganda ishlaydi:

const foydalanuvchi = {};

const tekshiruvchi = new Proxy(foydalanuvchi, {
  set(target, xususiyat, qiymat) {
    //               ↑       ↑
    //         xususiyat nomi  yangi qiymat

    if (xususiyat === "yosh") {
      if (typeof qiymat !== "number") {
        throw new TypeError("Yosh son bo'lishi kerak");
      }
      if (qiymat < 0 || qiymat > 150) {
        throw new RangeError("Yosh 0 dan 150 gacha bo'lishi kerak");
      }
    }

    target[xususiyat] = qiymat; // ← validatsiyadan o'tdi — yozish
    return true;                 // ← set trap true qaytarishi shart
  }
});

tekshiruvchi.ism  = "Jasur"; // ✅
tekshiruvchi.yosh = 25;      // ✅
tekshiruvchi.yosh = -5;      // ❌ RangeError: Yosh 0 dan 150 gacha bo'lishi kerak
tekshiruvchi.yosh = "katta"; // ❌ TypeError: Yosh son bo'lishi kerak

Bu — setter ning kuchaytirilgan versiyasi. Oldindan xususiyat nomini bilmasdan ham, har qanday xususiyatni tekshirish mumkin.


has trap — in operatorini ushlab olish

const doira = new Proxy({ radius: 5 }, {
  has(target, xususiyat) {
    // "in" operatori chaqirilganda
    if (xususiyat === "area") return true; // ← "area" borday ko'rsatish
    return xususiyat in target;
  },

  get(target, xususiyat) {
    if (xususiyat === "area") {
      return Math.PI * target.radius ** 2;
    }
    return target[xususiyat];
  }
});

console.log("area" in doira);  // true ← has trap
console.log(doira.area);       // 78.53... ← get trap

deleteProperty trap — o'chirishni ushlab olish

const himoyalangan = new Proxy({ maxfiy: "sir", ommaviy: "ma'lumot" }, {
  deleteProperty(target, xususiyat) {
    if (xususiyat === "maxfiy") {
      throw new Error("Maxfiy xususiyatni o'chirib bo'lmaydi!");
    }
    delete target[xususiyat];
    return true;
  }
});

delete himoyalangan.ommaviy; // ✅
delete himoyalangan.maxfiy;  // ❌ Error: Maxfiy xususiyatni o'chirib bo'lmaydi!

Reflect — trap ichida xavfsiz murojaat

Trap ichida haqiqiy ob'ektga murojaat qilishda target[xususiyat] o'rniga Reflect ishlatish tavsiya etiladi:

const proksi = new Proxy(ob, {
  get(target, xususiyat, receiver) {
    console.log(`"${xususiyat}" o'qildi`);
    return Reflect.get(target, xususiyat, receiver); // ← to'g'ri murojaat
  },

  set(target, xususiyat, qiymat, receiver) {
    console.log(`"${xususiyat}" = ${qiymat}`);
    return Reflect.set(target, xususiyat, qiymat, receiver);
  }
});

Reflect — JavaScript ning ichki operatsiyalarini funksiya sifatida. Har bir Proxy trap uchun mos Reflect metodi bor.

Nima uchun target[x] o'rniga Reflect.get?

receiver — meros (inheritance) zanjirida to'g'ri this ni saqlash uchun. Oddiy target[x] ba'zi holatlarda this ni noto'g'ri ko'rsatishi mumkin. Reflect.get(target, x, receiver) bu muammoni hal qiladi.


Amaliy misol — kuzatuvchi (observable)

function kuzatuvchiYarat(ob, callback) {
  return new Proxy(ob, {
    set(target, xususiyat, qiymat) {
      const eski = target[xususiyat];
      Reflect.set(target, xususiyat, qiymat);

      if (eski !== qiymat) {
        callback(xususiyat, eski, qiymat); // ← o'zgardi!
      }
      return true;
    }
  });
}

const holat = kuzatuvchiYarat(
  { soni: 0, matn: "" },
  function(xususiyat, eski, yangi) {
    console.log(`${xususiyat}: ${eski} → ${yangi}`);
  }
);

holat.soni = 5;   // "soni: 0 → 5"
holat.matn = "salom"; // "matn:  → salom"
holat.soni = 5;   // ← o'zgarmadi, callback ishlamadi

Tez-tez uchraydigan xatolar

set trap da true qaytarmaslik:

// ❌ — strict modeda TypeError
set(target, prop, value) {
  target[prop] = value;
  // return yo'q → undefined → false → xato
}

// ✅
set(target, prop, value) {
  target[prop] = value;
  return true; // ← muvaffaqiyatli yozildi
}

Proksi orqali emas, haqiqiy ob'ektga murojaat:

const asl = { son: 0 };
const proksi = new Proxy(asl, { set(t, p, v) { console.log("trap"); t[p]=v; return true; } });

proksi.son = 5; // "trap" — proksi orqali
asl.son = 10;   // ← trap ishlamadi — to'g'ridan asl ob'ektga

Proksi orqali ishlash kerak bo'lsa — faqat proksi ishlatiladi, asl ob'ektga to'g'ridan murojaat qilinmaydi.


Xulosa

Proxy — ob'ektga har murojaatni ushlab oluvchi qatlam. Kotib o'xshatmasi: har murojaat kotib orqali.

Traplar: get (o'qish), set (yozish), has (in), deleteProperty (o'chirish) va boshqalar.

Reflect — Proxy trap ichida haqiqiy ob'ektga to'g'ri murojaat uchun.

Ishlatilishi: validatsiya, logging, himoya, reaktiv holat (Vue.js ichki mexanizmi aynan Proxy da qurilgan).


Keyingi dars

61-dars: WeakMap va WeakSet — nima uchun kerak