IlmHamroh
JavaScript Full-stack/12-qism. JavaScript: ilg'or mavzular va kod sifati20/44-dars22 daqiqa
Mundarija (37)

JavaScript Proxy va Reflect: obyekt bilan bo'ladigan har bir amalni ushlab qolish

Qisqacha: Proxy — obyektning oldiga qo'yiladigan "vositachi": new Proxy(nishon, handler). Proxy orqali bo'ladigan har bir amal — o'qish, yozish, in, delete, chaqirish — avval handler'dagi trap funksiyasiga boradi. Trap amalni tekshirishi, o'zgartirishi yoki taqiqlashi mumkin. Reflect esa har bir amalning "odatdagi" bajarilishini beradi: trap ichida Reflect.get(...) yozsangiz, amal xuddi proxy'siz bo'lgandek bajariladi.

Bu darsda

  • new Proxy(nishon, handler) yasab, proxy va nishon qanday bog'langanini tushuntira olasiz.
  • get, set, has, deleteProperty, apply, construct trap'larini yoza olasiz.
  • Reflect nima uchun kerakligini va receiver argumenti getter'larni qanday qutqarishini bilasiz.
  • Proxy bilan validatsiya, faqat o'qiladigan ko'rinish va kichik reaktivlik tizimini yasaysiz.
  • Proxy ishlamaydigan joylarni (Map, private maydonlar, muzlatilgan obyekt) va uning narxini bilasiz.

Oldin bilishingiz kerak: Getter va setter, Xususiyat deskriptorlari, Well-known symbols, class sintaksisi.

1. Nega bu kerak?

Oldingi darsda tagged template bilan template literalni yig'ish jarayonini qo'lga oldik. Bugun yanada kattaroq narsani qo'lga olamiz: obyekt bilan bo'ladigan har bir amalni.

«Bahor»ning menyusi oddiy obyektda turadi. Stajyor Sardor narxlarni yangilayotganda ikki xato qildi:

js
const menyu = { osh: 35000, manti: 30000 };

menyu.osh = "35 ming";
console.log(menyu.lagmon); // undefined

Birinchi qatorda narx satrga aylandi — keyin hisob-kitob buziladi. Ikkinchi qatorda lag'mon o'rniga lagmon yozildi va JavaScript jimgina undefined qaytardi. Ikkala xato ham hech qanday ogohlantirishsiz o'tdi.

Getter va setter darsida yozishni tekshirishni o'rgangan edik. Lekin setter har bir xususiyat uchun alohida yoziladi. Menyuda o'ttizta taom bo'lsa — o'ttizta setter. Ertaga yangi taom qo'shilsa, unga setter yo'q. Mavjud bo'lmagan xususiyatni o'qishni esa setter umuman ushlamaydi.

Bizga bitta joy kerak: "bu obyektga qaysi xususiyat yozilsa ham — avval menga ko'rsat". Shunday joyni Proxy beradi.

Hayotdan o'xshatish: katta idoradagi qabulxona. Rahbarga to'g'ridan-to'g'ri kirib bo'lmaydi. Har bir tashrif, har bir hujjat avval kotibdan o'tadi. Kotib ba'zisini o'tkazadi, ba'zisini qaytaradi, ba'zisiga "rahbar band, keyin keling" deydi. Rahbar — obyekt, kotib — proxy.

2. Birinchi proxy

2.1 new Proxy(nishon, handler)

Proxy (proksi, vositachi) — boshqa obyektning o'rniga turadigan va unga bo'ladigan amallarni ushlab qoladigan maxsus obyekt. U ikki argument bilan yasaladi:

  • nishon (target) — asl obyekt, amallar oxir-oqibat unga boradi;
  • handler (ishlov beruvchi) — trap'lar yozilgan oddiy obyekt. Trap (tuzoq) — muayyan amalni ushlab qoladigan metod: get — o'qishni, set — yozishni ushlaydi.

Qabulxona o'xshatishida: rahbar — nishon, kotib — proxy, kotibga berilgan yo'riqnoma — handler, yo'riqnomaning har bir bandi ("hujjat kelsa — tekshir") — trap.

Handler bo'sh bo'lsa, proxy hech narsani ushlamaydi — hamma amal nishonga o'tadi:

js
const menyu = { osh: 35000 };
const proksi = new Proxy(menyu, {});

proksi.manti = 30000;

console.log(menyu); // { osh: 35000, manti: 30000 }
console.log(proksi.osh); // 35000
console.log(proksi === menyu); // false

Proxy orqali yozilgan manti asl menyu ga tushdi. Proxy o'zida ma'lumot saqlamaydi — u faqat eshik. Lekin u boshqa obyekt: === false qaytardi.

2.2 get trap: o'qishni ushlash

Endi kotibga birinchi vazifani beramiz: mavjud bo'lmagan taomni so'rashsa, jimgina undefined qaytarmasin, xato bersin:

js
const menyu = { osh: 35000, manti: 30000 };

const qatiyMenyu = new Proxy(menyu, {
  get(nishon, kalit) {
    if (!(kalit in nishon)) {
      throw new Error(`Menyuda "${String(kalit)}" yo'q`);
    }
    return nishon[kalit];
  },
});

console.log(qatiyMenyu.osh);
console.log(qatiyMenyu.lagmon);

Konsolda:

text
35000
Error: Menyuda "lagmon" yo'q

get trap uchta argument oladi: nishon — asl obyekt, kalit — o'qilayotgan xususiyat nomi, uchinchisi — receiver, uni keyinroq ko'ramiz. Trap nima qaytarsa, o'qish natijasi shu bo'ladi.

String(kalit) nega kerak? Kalit satr yoki symbol bo'lishi mumkin. Template literal symbol'ni satrga o'zi aylantira olmaydi va TypeError beradi. String() esa aylantiradi.

Endi Sardorning lagmon xatosi darhol, aniq xabar bilan ko'rinadi. Xato qanchalik erta chiqsa, uni topish shunchalik oson (Xato strategiyasi darsidagi "fail fast").

2.3 set trap: yozishni tekshirish

Ikkinchi vazifa — narx faqat musbat butun son bo'lsin:

js
const menyu = { osh: 35000 };

const himoyaliMenyu = new Proxy(menyu, {
  set(nishon, kalit, qiymat) {
    if (!Number.isInteger(qiymat) || qiymat <= 0) {
      throw new TypeError(`Narx musbat butun son bo'lsin: ${qiymat}`);
    }
    nishon[kalit] = qiymat;
    return true;
  },
});

himoyaliMenyu.manti = 30000;
console.log(menyu.manti); // 30000

try {
  himoyaliMenyu.osh = "35 ming";
} catch (xato) {
  console.log(xato.message); // Narx musbat butun son bo'lsin: 35 ming
}
console.log(menyu.osh); // 35000

set to'rt argument oladi: nishon, kalit, qiymat va receiver. Ikkita qoida bor:

  1. Yozishni o'zingiz bajarasiz — nishon[kalit] = qiymat. Trap bajarmasa, hech narsa yozilmaydi.
  2. Muvaffaqiyatli bo'lsa, true qaytaring.

Ikkinchi qoida muhim. Trap false qaytarsa yoki hech narsa qaytarmasa (undefined — "yolg'on" qiymat), qat'iy rejimda JavaScript xato tashlaydi. Bu xatoni «Ko'p uchraydigan xatolar» bo'limida ko'ramiz.

Yangi taom qo'shilsa ham (manti) trap ishladi. Bitta trap — barcha xususiyatlar uchun, hozirgilari va kelajakdagilari uchun ham. Setter bilan bu imkonsiz edi.

Tekshirib ko'ring: himoyaliMenyu orqali emas, to'g'ridan-to'g'ri menyu.osh = "arzon" deb yozilsa, trap ishlaydimi?

Javob

Yo'q. Trap faqat proxy orqali bo'lgan amallarni ushlaydi. Asl obyekt himoyasiz qoladi. Shuning uchun amalda asl obyektga havolani hech kimga bermaydi: proxy yasaladi va faqat u tarqatiladi. Masalan, nishonni proxy yasaydigan funksiya ichida yaratib, tashqariga faqat proxy'ni qaytarish mumkin.

3. Boshqa trap'lar

3.1 has va deleteProperty

in operatori has trap'ini, delete operatori esa deleteProperty trap'ini chaqiradi. «Bahor» menyusida "asosiy" taomlarni o'chirib bo'lmasin, in esa katta-kichik harfga qaramasin:

js
const menyu = { osh: 35000, manti: 30000, choy: 5000 };
const asosiylar = ["osh"];

const menyuProksi = new Proxy(menyu, {
  has(nishon, kalit) {
    return String(kalit).toLowerCase() in nishon;
  },
  deleteProperty(nishon, kalit) {
    if (asosiylar.includes(kalit)) {
      console.log(`${kalit} — asosiy taom, o'chirilmaydi`);
      return true;
    }
    return delete nishon[kalit];
  },
});

console.log("OSH" in menyuProksi); // true
delete menyuProksi.osh;
delete menyuProksi.choy;
console.log(Object.keys(menyu)); // [ 'osh', 'manti' ]

Konsolda:

text
true
osh — asosiy taom, o'chirilmaydi
[ 'osh', 'manti' ]

deleteProperty ham set kabi true/false qaytaradi. Biz osh uchun true qaytardik — "amal bajarildi" deb aytdik, lekin aslida o'chirmadik. Bu xato chiqarmaslik uchun. Haqiqatan ham taqiqlash kerak bo'lsa — false qaytaring, qat'iy rejimda TypeError chiqadi.

3.2 apply: funksiya chaqiruvini ushlash

Proxy nishoni funksiya ham bo'lishi mumkin — funksiya ham obyekt (IIFE va funksiya obyekt sifatida). Unda apply trap'i har bir chaqiruvni ushlaydi. Masalan, hisob funksiyasi necha marta va qanday argumentlar bilan chaqirilganini yozib boramiz:

js
function jamiHisobla(narx, soni) {
  return narx * soni;
}

const kuzatilgan = new Proxy(jamiHisobla, {
  apply(nishon, thisQiymati, argumentlar) {
    console.log(`chaqiruv: ${argumentlar.join(", ")}`);
    return Reflect.apply(nishon, thisQiymati, argumentlar);
  },
});

console.log(kuzatilgan(35000, 2));
console.log(kuzatilgan(5000, 3));

Konsolda:

text
chaqiruv: 35000, 2
70000
chaqiruv: 5000, 3
15000

apply uch argument oladi: asl funksiya, this qiymati va argumentlar massivi. Oxirgi qatorda asl funksiyani Reflect.apply bilan chaqirdik — u apply metodi bilan bir xil ishlaydi. Reflect nimaligini keyingi bo'limda ko'ramiz.

3.3 construct: new ni ushlash

construct trap'i new bilan chaqiruvni ushlaydi. Unda nusxa yaratilishidan oldin argumentlarni tekshirish mumkin:

js
class Bron {
  constructor(ism, soni) {
    this.ism = ism;
    this.soni = soni;
  }
}

const TekshiriladiganBron = new Proxy(Bron, {
  construct(nishon, argumentlar) {
    const [, soni] = argumentlar;
    if (soni > 20) throw new RangeError("Ko'pi bilan 20 kishi");
    return Reflect.construct(nishon, argumentlar);
  },
});

const bron = new TekshiriladiganBron("Dilshod aka", 4);
console.log(bron instanceof Bron, bron.soni); // true 4

construct albatta obyekt qaytarishi kerak. Reflect.construct — new nishon(...argumentlar) ning o'zi.

3.4 Barcha trap'lar bir jadvalda

Trap'lar 13 ta. Har biri bitta "ichki amal"ga mos keladi. Eng ko'p ishlatiladiganlari:

Trap Qaysi amalni ushlaydi Misol
get o'qish p.osh, p["osh"]
set yozish p.osh = 1
has in "osh" in p
deleteProperty o'chirish delete p.osh
ownKeys kalitlar ro'yxati Object.keys(p), for...in
apply chaqirish p(1, 2)
construct new new P()

Qolganlari: getOwnPropertyDescriptor, defineProperty (deskriptorlar bilan ishlash), getPrototypeOf, setPrototypeOf, isExtensible, preventExtensions. Ular kamroq kerak bo'ladi — ro'yxati MDN'da.

ownKeys qiziq misol beradi. U pastki chiziq bilan boshlanadigan "ichki" kalitlarni ro'yxatdan yashira oladi:

js
const mehmon = { ism: "Dilshod aka", _ichkiId: 742 };

const korinish = new Proxy(mehmon, {
  ownKeys(nishon) {
    return Reflect.ownKeys(nishon).filter(
      (kalit) => !String(kalit).startsWith("_"),
    );
  },
});

console.log(Object.keys(korinish)); // [ 'ism' ]
console.log(JSON.stringify(korinish)); // {"ism":"Dilshod aka"}
console.log(korinish._ichkiId); // 742

Oxirgi qatorga e'tibor bering: _ichkiId ro'yxatdan yashirindi, lekin to'g'ridan-to'g'ri o'qilsa — bor. ownKeys faqat sanashni ushlaydi, o'qishni emas. Haqiqiy yashirish uchun private maydonlar yoki closure kerak.

4. Reflect: amalning odatdagi bajarilishi

4.1 Nega Reflect?

Trap'larda biz ba'zan nishon[kalit] yozdik, ba'zan Reflect.apply(...). Farqi nima?

Reflect — statik metodlar to'plami. Uning har bir metodi bitta trap'ga mos keladi va xuddi shu nomda: Reflect.get, Reflect.set, Reflect.has, Reflect.deleteProperty, Reflect.ownKeys — jami 13 ta, trap'lar soni bilan bir xil. Har biri amalni odatdagidek, proxy'siz bajaradi. Trap ichida "endi odatiy ishni qil" deyish uchun eng to'g'ri yo'l — xuddi shu nomdagi Reflect metodini xuddi shu argumentlar bilan chaqirish.

Reflect — klass emas, new Reflect() yo'q. U Math ga o'xshaydi: shunchaki funksiyalar to'plami (Math obyekti).

Ikki foydali farqi bor:

js
const muzlagan = Object.freeze({ osh: 35000 });

console.log(Reflect.set(muzlagan, "osh", 1)); // false
console.log(Reflect.has(muzlagan, "osh")); // true
console.log(Reflect.ownKeys({ a: 1, [Symbol("b")]: 2 }));

Konsolda:

text
false
true
[ 'a', Symbol(b) ]
  • Reflect.set xato tashlamaydi — muvaffaqiyatni true/false bilan aytadi. Bu set trap'i qaytarishi kerak bo'lgan qiymatning aynan o'zi. Shuning uchun return Reflect.set(...) — tayyor javob.
  • Reflect.ownKeys satr kalitlarni ham, symbol kalitlarni ham beradi. Symbol darsida "buni keyin o'rganamiz" degan edik — mana u. Object.keys symbol'larni ko'rsatmaydi.

Operatorlar (in, delete) ham funksiyaga aylandi: Reflect.has(obyekt, "osh") — "osh" in obyekt bilan bir xil. Funksiyani esa callback qilib uzatish mumkin, operatorni — yo'q.

4.2 receiver: getter'dagi this

get trap'ining uchinchi argumenti — receiver (qabul qiluvchi): amal kimdan boshlanganini bildiradi, getter ichida this aynan shu bo'lishi kerak. Ko'pincha bu proxy'ning o'zi. Lekin meros bo'lsa — boshqa obyekt. Farq getter'da ko'rinadi:

js
const mehmon = {
  _ism: "Mehmon",
  get ism() {
    return this._ism;
  },
};

const yomon = new Proxy(mehmon, {
  get(nishon, kalit) {
    return nishon[kalit];
  },
});
const yaxshi = new Proxy(mehmon, {
  get(nishon, kalit, receiver) {
    return Reflect.get(nishon, kalit, receiver);
  },
});

const a = Object.create(yomon);
a._ism = "Dilshod aka";
const b = Object.create(yaxshi);
b._ism = "Dilshod aka";

console.log(a.ism); // Mehmon
console.log(b.ism); // Dilshod aka

a va b — proxy'dan meros olgan obyektlar (Object.create va prototipal meros). a.ism o'qilganda: a da ism yo'q → prototipga, ya'ni proxy'ga boriladi → get trap ishlaydi.

  • yomon trap'i nishon[kalit] yozdi. Getter this sifatida nishonni — mehmon ni oldi. Natija — "Mehmon", a ning o'z _ism i e'tiborsiz qoldi.
  • yaxshi trap'i receiver ni uzatdi. receiver — b, ya'ni o'qish boshlangan obyekt. Getter this sifatida b ni oldi — to'g'ri natija.

Qoida qisqa: trap ichida odatiy amal uchun Reflect.* ni barcha argumentlar bilan chaqiring. Shunda getter, setter va meros to'g'ri ishlaydi.

Butun jarayonni ketma-ketlik sxemasida kuzating — kod, proxy, trap, Reflect va nishon:

sequenceDiagram
  participant K as Kod
  participant P as Proxy
  participant H as get trap
  participant R as Reflect.get
  participant N as Nishon
  K->>P: b.ism
  P->>H: (nishon, "ism", receiver)
  H->>R: Reflect.get(nishon, "ism", receiver)
  R->>N: getter'ni this = receiver bilan chaqir
  N-->>R: "Dilshod aka"
  R-->>H: "Dilshod aka"
  H-->>K: natija

Trap — "qabulxona", Reflect — "odatdagi yo'l". Trap oraga o'z tekshiruvini qo'shadi, qolganini Reflect ga topshiradi.

Tekshirib ko'ring: set trap'ida nishon[kalit] = qiymat; return true; o'rniga nima yozish yaxshiroq va nega?

Javob

return Reflect.set(nishon, kalit, qiymat, receiver);. Birinchidan, u setter'larga to'g'ri this beradi (receiver). Ikkinchidan, yozish imkonsiz bo'lsa (masalan, xususiyat writable: false), u false qaytaradi — trap esa yolg'ondan true deb aytmaydi.

5. Amaliy naqshlar

5.1 Faqat o'qiladigan ko'rinish

Object.freeze obyektni hamma uchun muzlatadi. Ba'zan boshqa narsa kerak: obyekt siz uchun o'zgaruvchan, boshqa modul uchun esa faqat o'qiladigan bo'lsin. Proxy bunday "ko'rinish" yasaydi:

js
function faqatOqish(obyekt) {
  return new Proxy(obyekt, {
    set() {
      throw new TypeError("Bu ko'rinish faqat o'qish uchun");
    },
    deleteProperty() {
      throw new TypeError("Bu ko'rinish faqat o'qish uchun");
    },
  });
}

const sozlama = { ochilish: "07:00", yopilish: "23:00" };
const korinish = faqatOqish(sozlama);

sozlama.yopilish = "00:00";
console.log(korinish.yopilish);
korinish.ochilish = "06:00";

Konsolda:

text
00:00
TypeError: Bu ko'rinish faqat o'qish uchun

Egasi sozlama ni o'zgartirdi — ko'rinish yangi qiymatni darhol ko'rsatdi, chunki nusxa emas, o'sha obyekt. Ko'rinish orqali yozishga urinish esa xato berdi. freeze dan farqi: muzlatish hamma uchun va abadiy, ko'rinish — faqat uni olgan kod uchun.

Bu yerda himoya sayoz: korinish.manzil.uy = 5 kabi ichma-ich yozish ushlanmaydi. Chuqur himoya uchun get trap'i ichki obyektlarni ham faqatOqish ga o'rab qaytarishi kerak — buni 3-mashqda qilasiz.

5.2 Reaktivlik: o'zgarishga javob berish

Holatdan UI chizish darsidan beri biz bitta qoidaga amal qildik: holatni o'zgartirdingizmi — render() ni chaqiring. Unutsangiz — ekran eskirib qoladi. Proxy bu chaqiruvni avtomatik qila oladi:

js
function reaktiv(holat, ozgarganda) {
  return new Proxy(holat, {
    set(nishon, kalit, qiymat, receiver) {
      const eski = nishon[kalit];
      const natija = Reflect.set(nishon, kalit, qiymat, receiver);
      if (natija && eski !== qiymat) ozgarganda(kalit, qiymat);
      return natija;
    },
  });
}

const savat = reaktiv({ soni: 0, jami: 0 }, (kalit, qiymat) => {
  console.log(`chiz: ${kalit} = ${qiymat}`);
});

savat.soni = 2;
savat.jami = 70000;
savat.soni = 2;

Konsolda:

text
chiz: soni = 2
chiz: jami = 70000

Uchinchi yozuv qiymatni o'zgartirmadi — chizish chaqirilmadi. Kod render() haqida umuman bilmaydi: u shunchaki savat.soni = 2 yozadi.

Bu — reaktivlik (reactivity): ma'lumot o'zgarganda unga bog'liq narsalar avtomatik yangilanadi. Vue 3 (hozir 3.5 versiya) aynan shunday ishlaydi: reactive() funksiyasi obyektni proxy'ga o'raydi. Xususiyat deskriptorlari darsida aytganimizdek, Vue 2 bu ishni Object.defineProperty bilan qilardi va yangi qo'shilgan xususiyatlarni ko'rmasdi. Proxy esa barcha kalitlarni ushlaydi. MobX va Valtio kutubxonalari ham proxy'ga qurilgan. Ularni keyinroq ko'rasiz, hozir bilish shart emas.

Tekshirib ko'ring: Savatda ichma-ich obyekt bo'lsa — reaktiv({ mehmon: { ism: "Ali" } }, ...) — va savat.mehmon.ism = "Vali" yozilsa, chizish chaqiriladimi?

Javob

Yo'q. savat.mehmon ni o'qish proxy'ning get orqali o'tdi (u trap'siz — odatdagidek), lekin qaytgan mehmon — oddiy obyekt, proxy emas. ism ga yozish to'g'ridan-to'g'ri unga tushdi va set trap'i hech narsani bilmadi. Vue bu muammoni shunday hal qiladi: get trap'i ichki obyektni qaytarishdan oldin uni ham reactive ga o'raydi. Xuddi shu usulni 3-mashqda faqat o'qiladigan ko'rinish uchun qilasiz.

6. Proxy chegaralari

6.1 Ichki slotli obyektlar: Map, Set, Date

Map ma'lumotni xususiyatlarda emas, ichki slotda saqlaydi — koddan ko'rinmaydigan yashirin joyda. Map.prototype.get esa this ning o'zida shu slot bor-yo'qligini tekshiradi. Proxy'da u yo'q:

js
const narxlar = new Map([["osh", 35000]]);
const proksi = new Proxy(narxlar, {});

proksi.get("osh");
text
TypeError: Method Map.prototype.get called on incompatible receiver #<Map>

Tarjimasi: "Map.prototype.get metodi mos kelmaydigan qabul qiluvchida chaqirildi." "Receiver" — this. get proxy orqali o'qildi, keyin this = proksi bilan chaqirildi. Proxy esa Map ning ichki slotiga ega emas. Konsol uni #<Map> deb ko'rsatadi — chalg'itmasin, bu proxy. Set, Date, Promise bilan ham xuddi shunday.

Yechim — metodni nishonga bog'lab qaytarish (bind):

js
const narxlar = new Map([["osh", 35000]]);

const proksi = new Proxy(narxlar, {
  get(nishon, kalit) {
    const qiymat = Reflect.get(nishon, kalit, nishon);
    if (typeof qiymat === "function") return qiymat.bind(nishon);
    return qiymat;
  },
});

console.log(proksi.get("osh"), proksi.size); // 35000 1

size — getter, shuning uchun Reflect.get ga receiver sifatida ham nishon ni berdik. Vue ham Map va Set uchun alohida, murakkabroq handler yozadi — oddiy obyekt handler'i ularga yaramaydi.

6.2 Private maydonlar

Klassning # maydonlari ham ichki slotga o'xshaydi. Getter proxy orqali chaqirilsa, this — proxy, unda esa # maydon yo'q:

js
class Hisob {
  #qoldiq = 100000;
  get qoldiq() {
    return this.#qoldiq;
  }
}

const proksi = new Proxy(new Hisob(), {});
console.log(proksi.qoldiq);
text
TypeError: Cannot read private member #qoldiq from an object whose class did not declare it

Tarjimasi: "#qoldiq private a'zosini uni e'lon qilmagan klass obyektidan o'qib bo'lmaydi." Bu brand check — private maydon faqat "o'z" obyektida bor. Yechim yuqoridagidek: getter'ni receiver emas, nishon bilan chaqirish (Reflect.get(nishon, kalit, nishon)).

6.3 Invariantlar: proxy yolg'on gapira olmaydi

Proxy ko'p narsani o'zgartira oladi, lekin hamma narsani emas. Til o'zgarmas shartlarni (invariant) — proxy hech qachon buza olmaydigan qoidalarni tekshiradi. Masalan, nishondagi xususiyat muzlatilgan bo'lsa, get trap'i boshqa qiymat qaytara olmaydi:

js
const muzlagan = Object.freeze({ narx: 35000 });

const yolgonchi = new Proxy(muzlagan, {
  get() {
    return 1;
  },
});

console.log(yolgonchi.narx);
text
TypeError: 'get' on proxy: property 'narx' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '35000' but got '1')

Tarjimasi: "Proxy'da get: narx nishonda faqat o'qiladigan va qayta sozlanmaydigan xususiyat, lekin proxy uning haqiqiy qiymatini qaytarmadi (35000 kutilgan, 1 keldi)." Muzlatish — va'da: "bu qiymat hech qachon o'zgarmaydi". Til proxy'ga bu va'dani buzishga ruxsat bermaydi.

6.4 O'chiriladigan proxy

Proxy.revocable proxy bilan birga revoke funksiyasini qaytaradi. Uni chaqirgandan keyin proxy orqali hech qanday amal ishlamaydi:

js
const { proxy, revoke } = Proxy.revocable({ kod: "1234" }, {});

console.log(proxy.kod); // 1234
revoke();

try {
  console.log(proxy.kod);
} catch (xato) {
  console.log(xato.message);
}

Konsolda:

text
1234
Cannot perform 'get' on a proxy that has been revoked

Bu vaqtinchalik ruxsat uchun qulay: boshqa modulga obyektni berasiz, ish tugagach — kirishni yopasiz. Asl obyektga havola sizda qoladi.

6.5 Narxi: tezlik

Har bir proxy amali — qo'shimcha funksiya chaqiruvi va tekshiruvlar. Bitta xususiyatni 10 million marta o'qishni uch usulda o'lchadik:

10 000 000 marta xususiyat o'qish
  • oddiy obyektV8 to'liq optimallashtirdi8,1 ms
  • bo'sh handler'li proxytrap yo'q, lekin proxy orqali193,2 ms
  • get + Reflect.gethar o'qishda trap chaqiruvi408,4 ms

Manba: O'lchov: Node 24.21, Windows 11, Intel Core i5-12500H, performance.now, 9 o'lchovning medianasi, 2026-10-05

Farq o'nlab barobar. Lekin raqamlarga diqqat bilan qarang: 400 ms — o'n million o'qish uchun. Bitta o'qish 0,04 mikrosekunddan kam. Formadagi validatsiya yoki tugma bosilgandagi holat uchun bu sezilmaydi. Million elementli massivni sikl ichida proxy orqali aylanish esa — sezilarli. Qoida: proxy — "chegara"da (modullar orasida, foydalanuvchi kiritgan ma'lumotda), issiq sikl ichida emas. O'lchashni Performansni o'lchash darsida o'rganamiz.

7. Ko'p uchraydigan xatolar

7.1 set trap'ida return true ni unutish

js
const proksi = new Proxy({}, {
  set(nishon, kalit, qiymat) {
    nishon[kalit] = qiymat;
  },
});

proksi.osh = 35000;
text
TypeError: 'set' on proxy: trap returned falsish for property 'osh'

Tarjimasi: "Proxy'da set: trap osh xususiyati uchun yolg'on qiymat qaytardi." Qiymat yozildi, lekin trap hech narsa qaytarmadi — undefined. Qat'iy rejimda (modullar va klasslar doim qat'iy) bu "yozish muvaffaqiyatsiz" deb tushuniladi. Tuzatish: return true yoki return Reflect.set(...).

7.2 Trap ichida proxy'ning o'ziga murojaat

js
const proksi = new Proxy({ osh: 35000 }, {
  get(nishon, kalit, receiver) {
    return receiver[kalit];
  },
});

proksi.osh;
text
RangeError: Maximum call stack size exceeded

Tarjimasi: "Chaqiruvlar stekining eng katta hajmidan oshib ketildi." receiver — proxy. receiver[kalit] yana get trap'ini chaqiradi, u yana… cheksiz rekursiya. Tuzatish: trap ichida nishonga Reflect.get(nishon, kalit, receiver) bilan murojaat qiling.

7.3 Getter'da nishon[kalit]

Xato chiqmaydi, lekin meros bo'lganda getter noto'g'ri this oladi («receiver: getter'dagi this» bo'limidagi "Mehmon"). Tuzatish: Reflect.get va Reflect.set ga receiver ni uzating.

7.4 Map, Set yoki # maydonli klassni o'rash

TypeError: ... incompatible receiver yoki Cannot read private member. Tuzatish: metodlarni bind(nishon) bilan qaytaring yoki bunday obyektlar uchun proxy o'rniga oddiy o'ram klass yozing.

7.5 Proxy'ni xavfsizlik devori deb o'ylash

Proxy faqat o'zi orqali o'tgan amallarni ushlaydi. Asl obyektga havola kimdadir bo'lsa, u proxy'ni chetlab o'tadi. Node konsoli ham proxy'ni emas, nishonni ko'rsatadi. Tuzatish: asl obyektni yashiring (closure ichida yarating) va proxy'ni qulaylik vositasi deb biling, qal'a devori deb emas.

8. Mashqlar

1-mashq (oson): Ombor qoldig'i

ombor proxy'sini yasang: mavjud bo'lmagan mahsulot so'ralsa undefined emas, 0 qaytarsin. Ishora: get trap'i va in operatori (in operatori).

js
console.log(ombor.guruch); // 50
console.log(ombor.sabzi); // 0
Yechim
js
const qoldiqlar = { guruch: 50, gosht: 12 };

const ombor = new Proxy(qoldiqlar, {
  get(nishon, kalit, receiver) {
    if (!(kalit in nishon)) return 0;
    return Reflect.get(nishon, kalit, receiver);
  },
});

console.log(ombor.guruch); // 50
console.log(ombor.sabzi); // 0
console.log(ombor.sabzi + 5); // 5

?? 0 ham shunga o'xshash ishlaydi, lekin uni har bir o'qishda yozish kerak. Proxy bilan qoida bir joyda. Diqqat: in prototipdagi kalitlarni ham ko'radi — ombor.toString funksiya qaytaradi, 0 emas.

2-mashq (o'rta): Bron tekshiruvchisi

bronYarat() funksiyasini yozing. U bo'sh bron obyektini proxy bilan qaytarsin. Qoidalar: soni — 1 dan 20 gacha butun son; soat — "10:00" dan "22:00" gacha satr (satrlarni <, > bilan solishtirish mumkin — bir xil uzunlikda ishlaydi); boshqa kalit yozilsa — xato. Xato bo'lsa RangeError tashlang. Ishora: set trap'i, switch (switch) va return Reflect.set(...).

js
const bron = bronYarat();
bron.soni = 4;
bron.soat = "19:00";
bron.stol = 7; // RangeError: Noma'lum maydon: stol
Yechim
js
function sonTogrimi(qiymat) {
  return Number.isInteger(qiymat) && qiymat >= 1 && qiymat <= 20;
}

function soatTogrimi(qiymat) {
  return typeof qiymat === "string" && qiymat.length === 5 &&
    qiymat >= "10:00" && qiymat <= "22:00";
}

function bronYarat() {
  return new Proxy({}, {
    set(nishon, kalit, qiymat, receiver) {
      switch (kalit) {
        case "soni":
          if (!sonTogrimi(qiymat)) {
            throw new RangeError(`Noto'g'ri soni: ${qiymat}`);
          }
          break;
        case "soat":
          if (!soatTogrimi(qiymat)) {
            throw new RangeError(`Bron 10:00–22:00: ${qiymat}`);
          }
          break;
        default:
          throw new RangeError(`Noma'lum maydon: ${String(kalit)}`);
      }
      return Reflect.set(nishon, kalit, qiymat, receiver);
    },
  });
}

const bron = bronYarat();
bron.soni = 4;
bron.soat = "19:00";
console.log(bron); // { soni: 4, soat: '19:00' }

for (const [kalit, qiymat] of [["soni", 25], ["soat", "23:30"]]) {
  try {
    bron[kalit] = qiymat;
  } catch (xato) {
    console.log(xato.message);
  }
}

Konsolda:

text
{ soni: 4, soat: '19:00' }
Noto'g'ri soni: 25
Bron 10:00–22:00: 23:30

Nishon funksiya ichida yaratildi va tashqariga faqat proxy chiqdi — tekshiruvni chetlab o'tadigan havola yo'q. "19:00" <= "22:00" satrlarni belgima-belgi solishtiradi va bir xil shakldagi vaqt uchun to'g'ri ishlaydi.

3-mashq (qiyin): Chuqur faqat o'qiladigan ko'rinish

«Faqat o'qiladigan ko'rinish» bo'limidagi faqatOqish sayoz edi. Uni chuqur qiling: get trap'i qiymat obyekt bo'lsa, uni ham faqatOqish ga o'rab qaytarsin. set va deleteProperty — false qaytarsin (qat'iy rejimda TypeError o'zi chiqadi). Ishora: typeof qiymat === "object" && qiymat !== null.

js
const bron = { mehmon: { ism: "Dilshod aka" }, soni: 4 };
const korinish = faqatOqish(bron);
korinish.mehmon.ism = "Boshqa"; // TypeError
Yechim
js
function faqatOqish(obyekt) {
  return new Proxy(obyekt, {
    get(nishon, kalit, receiver) {
      const qiymat = Reflect.get(nishon, kalit, receiver);
      if (typeof qiymat === "object" && qiymat !== null) {
        return faqatOqish(qiymat);
      }
      return qiymat;
    },
    set() {
      return false;
    },
    deleteProperty() {
      return false;
    },
  });
}

const bron = { mehmon: { ism: "Dilshod aka" }, soni: 4 };
const korinish = faqatOqish(bron);

try {
  korinish.mehmon.ism = "Boshqa";
} catch (xato) {
  console.log(xato.message);
}
console.log(bron.mehmon.ism); // Dilshod aka

Konsolda:

text
'set' on proxy: trap returned falsish for property 'ism'
Dilshod aka

korinish.mehmon endi oddiy obyekt emas, yangi proxy. Uning set trap'i ham false qaytaradi. Bitta kamchilik: har o'qishda yangi proxy yasaladi, ya'ni korinish.mehmon === korinish.mehmon — false. Kutubxonalar buni WeakMap kesh bilan hal qiladi: bir obyekt uchun proxy bir marta yasaladi.

4-mashq: Vazifalar qadami — reaktiv holat tajribasi

vazifalar ilovasida render() kuzatuvchi orqali chaqiriladi — bu kanonik va uni o'zgartirmaymiz. Proxy bilan muqobil yo'lni mashqlar repoda sinab ko'ramiz: kurs/mashqlar/12/20-proxy/reaktiv-holat.mjs. Unda { filtr: "hammasi", tahrirId: null } holati reaktiv bilan o'ralsin. O'zgarish bo'lganda chiz funksiyasi "chizildi: filtr=…, tahrirId=…" deb yozsin. Bitta qo'shimcha: ketma-ket ikki o'zgarish bo'lsa, chiz bir marta chaqirilsin. Ishora: queueMicrotask bilan chizishni keyinroqqa qoldiring va bayroq bilan takrorni to'xtating.

Yechim
js
function reaktiv(holat, chiz) {
  let rejalashtirilgan = false;
  return new Proxy(holat, {
    set(nishon, kalit, qiymat, receiver) {
      const natija = Reflect.set(nishon, kalit, qiymat, receiver);
      if (!rejalashtirilgan) {
        rejalashtirilgan = true;
        queueMicrotask(() => {
          rejalashtirilgan = false;
          chiz(nishon);
        });
      }
      return natija;
    },
  });
}

const holat = reaktiv({ filtr: "hammasi", tahrirId: null }, (h) => {
  console.log(`chizildi: filtr=${h.filtr}, tahrirId=${h.tahrirId}`);
});

holat.filtr = "faol";
holat.tahrirId = 3;
console.log("sinxron kod tugadi");

Konsolda:

text
sinxron kod tugadi
chizildi: filtr=faol, tahrirId=3

Ikki yozuv — bitta chizish. Birinchi set chizishni microtask navbatiga qo'ydi, ikkinchisi bayroqni ko'rib, qayta qo'ymadi. Sinxron kod tugagach, microtask bajarildi va oxirgi holatni chizdi. Vue ham yangilanishlarni xuddi shunday "partiyalab" bajaradi. Terminalda: node 12/20-proxy/reaktiv-holat.mjs.

bash
git add 12/20-proxy
git commit -m "12/20: proxy bilan reaktiv holat tajribasi"

9. Real ishda

  • Reaktiv freymvorklar. Vue 3 (reactive, ref), MobX, Valtio, Solid'ning store'lari — hammasi proxy. Vue'da holat obyektini konsolga chiqarsangiz, Proxy(Object) {...} ko'rasiz — endi bu nima ekanini bilasiz.
  • Immer. "Joyida o'zgartirgandek" yozib, o'zgarmas nusxa oladigan kutubxona — u ham proxy ustiga qurilgan. Uni Immutability chuqur darsida ko'ramiz.
  • Validatsiya va sozlamalar. Konfiguratsiya obyektida noma'lum kalitni darhol ushlash, "faqat o'qish" ko'rinishlari, API javoblarini tekshirish.
  • Test vositalari. Test kutubxonalaridagi (Vitest, Jest — 22-qismda) "josus" funksiyalar (spy) chaqiruvlarni apply trap'iga o'xshash usulda yozib boradi — xuddi «apply: funksiya chaqiruvini ushlash» bo'limidagi kuzatilgan kabi.
  • Intervyu. "Vue 3 nega Proxy'ga o'tdi?", "Reflect nima uchun kerak?", "Proxy bilan validatsiya yozing" — middle va senior intervyularida uchraydi.

Xulosa

  • new Proxy(nishon, handler) — vositachi: proxy orqali bo'lgan amallar handler'dagi trap'larga boradi, bo'sh handler — hammasi nishonga.
  • Asosiy trap'lar: get, set, has, deleteProperty, ownKeys, apply, construct; set va deleteProperty true qaytarishi shart.
  • Reflect.* — har trap uchun odatdagi xulq; trap ichida xuddi shu argumentlar bilan chaqiring, ayniqsa receiver ni.
  • Naqshlar: validatsiya, faqat o'qiladigan ko'rinish, reaktivlik (Vue 3 shu yo'l bilan ishlaydi).
  • Chegaralar: Map/Set/Date va # maydonlar (bind(nishon) kerak), invariantlar, asl obyekt himoyasiz, o'qish o'nlab barobar sekinroq.

Keyingi dars: eval, new Function va with — satrdan kod bajaradigan uchta vosita: nega ular xavfli va sekin, CSP ularni qanday to'xtatadi va qachon hali ham ishlatiladi.

Manbalar

  • MDN: "Proxy", "Proxy() constructor" (handler va trap'lar), "Reflect" — developer.mozilla.org
  • ECMAScript 2025 Language Specification, 10.5 "Proxy Object Internal Methods and Internal Slots" (invariantlar), 28.1 "The Reflect Object" — tc39.es/ecma262
  • Vue.js: "Reactivity in Depth" — vuejs.org/guide/extras/reactivity-in-depth
  • javascript.info: "Proxy and Reflect" — javascript.info/proxy
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript Proxy va Reflect: obyekt bilan bo'ladigan har bir amalni ushlab qolish — IlmHamroh