Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi proxy
- 2.1 new Proxy(nishon, handler)
- 2.2 get trap: o'qishni ushlash
- 2.3 set trap: yozishni tekshirish
- 3. Boshqa trap'lar
- 3.1 has va deleteProperty
- 3.2 apply: funksiya chaqiruvini ushlash
- 3.3 construct: new ni ushlash
- 3.4 Barcha trap'lar bir jadvalda
- 4. Reflect: amalning odatdagi bajarilishi
- 4.1 Nega Reflect?
- 4.2 receiver: getter'dagi this
- 5. Amaliy naqshlar
- 5.1 Faqat o'qiladigan ko'rinish
- 5.2 Reaktivlik: o'zgarishga javob berish
- 6. Proxy chegaralari
- 6.1 Ichki slotli obyektlar: Map, Set, Date
- 6.2 Private maydonlar
- 6.3 Invariantlar: proxy yolg'on gapira olmaydi
- 6.4 O'chiriladigan proxy
- 6.5 Narxi: tezlik
- 7. Ko'p uchraydigan xatolar
- 7.1 set trap'ida return true ni unutish
- 7.2 Trap ichida proxy'ning o'ziga murojaat
- 7.3 Getter'da nishon[kalit]
- 7.4 Map, Set yoki # maydonli klassni o'rash
- 7.5 Proxy'ni xavfsizlik devori deb o'ylash
- 8. Mashqlar
- 1-mashq (oson): Ombor qoldig'i
- 2-mashq (o'rta): Bron tekshiruvchisi
- 3-mashq (qiyin): Chuqur faqat o'qiladigan ko'rinish
- 4-mashq: Vazifalar qadami — reaktiv holat tajribasi
- 9. Real ishda
- Xulosa
- Manbalar
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.Reflectesa har bir amalning "odatdagi" bajarilishini beradi: trap ichidaReflect.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,constructtrap'larini yoza olasiz.Reflectnima uchun kerakligini vareceiverargumenti 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:
const menyu = { osh: 35000, manti: 30000 };
menyu.osh = "35 ming";
console.log(menyu.lagmon); // undefinedBirinchi 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:
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); // falseProxy 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:
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:
35000
Error: Menyuda "lagmon" yo'qget 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:
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); // 35000set to'rt argument oladi: nishon, kalit, qiymat va receiver. Ikkita qoida bor:
- Yozishni o'zingiz bajarasiz —
nishon[kalit] = qiymat. Trap bajarmasa, hech narsa yozilmaydi. - Muvaffaqiyatli bo'lsa,
trueqaytaring.
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:
himoyaliMenyuorqali emas, to'g'ridan-to'g'rimenyu.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:
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:
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:
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:
chaqiruv: 35000, 2
70000
chaqiruv: 5000, 3
15000apply 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:
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 4construct 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:
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); // 742Oxirgi 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:
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:
false
true
[ 'a', Symbol(b) ]Reflect.setxato tashlamaydi — muvaffaqiyatnitrue/falsebilan aytadi. Busettrap'i qaytarishi kerak bo'lgan qiymatning aynan o'zi. Shuning uchunreturn Reflect.set(...)— tayyor javob.Reflect.ownKeyssatr kalitlarni ham, symbol kalitlarni ham beradi. Symbol darsida "buni keyin o'rganamiz" degan edik — mana u.Object.keyssymbol'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:
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 akaa 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.
yomontrap'inishon[kalit]yozdi. Getterthissifatida nishonni —mehmonni oldi. Natija —"Mehmon",aning o'z_ismi e'tiborsiz qoldi.yaxshitrap'ireceiverni uzatdi.receiver—b, ya'ni o'qish boshlangan obyekt. Getterthissifatidabni 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: natijaTrap — "qabulxona", Reflect — "odatdagi yo'l". Trap oraga o'z tekshiruvini qo'shadi, qolganini Reflect ga topshiradi.
Tekshirib ko'ring:
settrap'idanishon[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:
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:
00:00
TypeError: Bu ko'rinish faqat o'qish uchunEgasi 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:
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:
chiz: soni = 2
chiz: jami = 70000Uchinchi 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" } }, ...)— vasavat.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:
const narxlar = new Map([["osh", 35000]]);
const proksi = new Proxy(narxlar, {});
proksi.get("osh");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):
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 1size — 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:
class Hisob {
#qoldiq = 100000;
get qoldiq() {
return this.#qoldiq;
}
}
const proksi = new Proxy(new Hisob(), {});
console.log(proksi.qoldiq);TypeError: Cannot read private member #qoldiq from an object whose class did not declare itTarjimasi: "#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:
const muzlagan = Object.freeze({ narx: 35000 });
const yolgonchi = new Proxy(muzlagan, {
get() {
return 1;
},
});
console.log(yolgonchi.narx);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:
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:
1234
Cannot perform 'get' on a proxy that has been revokedBu 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:
- 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
const proksi = new Proxy({}, {
set(nishon, kalit, qiymat) {
nishon[kalit] = qiymat;
},
});
proksi.osh = 35000;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
const proksi = new Proxy({ osh: 35000 }, {
get(nishon, kalit, receiver) {
return receiver[kalit];
},
});
proksi.osh;RangeError: Maximum call stack size exceededTarjimasi: "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).
console.log(ombor.guruch); // 50
console.log(ombor.sabzi); // 0Yechim
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(...).
const bron = bronYarat();
bron.soni = 4;
bron.soat = "19:00";
bron.stol = 7; // RangeError: Noma'lum maydon: stolYechim
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:
{ soni: 4, soat: '19:00' }
Noto'g'ri soni: 25
Bron 10:00–22:00: 23:30Nishon 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.
const bron = { mehmon: { ism: "Dilshod aka" }, soni: 4 };
const korinish = faqatOqish(bron);
korinish.mehmon.ism = "Boshqa"; // TypeErrorYechim
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 akaKonsolda:
'set' on proxy: trap returned falsish for property 'ism'
Dilshod akakorinish.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
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:
sinxron kod tugadi
chizildi: filtr=faol, tahrirId=3Ikki 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.
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
applytrap'iga o'xshash usulda yozib boradi — xuddi «apply: funksiya chaqiruvini ushlash» bo'limidagikuzatilgankabi. - 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;setvadeletePropertytrueqaytarishi shart. Reflect.*— har trap uchun odatdagi xulq; trap ichida xuddi shu argumentlar bilan chaqiring, ayniqsareceiverni.- Naqshlar: validatsiya, faqat o'qiladigan ko'rinish, reaktivlik (Vue 3 shu yo'l bilan ishlaydi).
- Chegaralar:
Map/Set/Dateva#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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!