Proxy & Reflect
Proxy nima, 13 xil trap (get, set, has, deleteProperty, apply, construct va h.k.), Reflect nima uchun Proxy bilan juftlashtirilgan, V8 ichida Proxy Hidden Class/Inline Cache'ni qanday chetlab o'tishi, invariant'lar va Vue 3 reactivity kabi real qo'llanilishlar.
42-dars
Proxy & Reflect
1. Kirish — meta-darajaga chiqish
Bu — Bosqich 3'ning oxirgi mavzusi, va u boshqalardan farq qiladi: Symbol, Iterator, Generator — bularning barchasi ma'lum bir protokolga tegishli edi. Proxy & Reflect esa — object bilan bo'ladigan har qanday amalni (o'qish, yozish, o'chirish, chaqirish, hatto in operatori) ushlab qolish imkonini beruvchi, JS'dagi eng meta (o'z-o'zini boshqaruvchi) mexanizm.
2. Muammo — Proxy'gacha nima yetishmasdi?
Getter/setter (Object.defineProperty orqali, oldingi mavzularda ko'rgan edik) sizga bitta, oldindan ma'lum propertyni nazorat qilish imkonini beradi:
const obj = {};
Object.defineProperty(obj, 'name', {
get() { return 'Ali'; },
set(v) { console.log('yozildi:', v); }
});
Lekin savol: agar siz har qanday, hatto hali mavjud bo'lmagan propertyga bo'ladigan murojaatni ushlab qolmoqchi bo'lsangiz-chi? Yoki delete obj.xni, yoki 'x' in objni, yoki hatto obj() (funksiya sifatida chaqirilishini)ni nazorat qilmoqchi bo'lsangiz? Getter/setter bunga ojiz — ular faqat oldindan e'lon qilingan property uchun ishlaydi.
Proxy — bu muammoni butunlay boshqa darajada hal qiladi: u butun object atrofida "qobiq" (wrapper) yaratadi, va har qanday fundamental operatsiyani ushlab qoladi.
3. Proxy — asosiy sintaksis
const target = { name: 'Ali', age: 25 };
const handler = {
get(obj, prop) {
console.log(`O'qildi: ${prop}`);
return obj[prop];
}
};
const proxy = new Proxy(target, handler);
proxy.name; // "O'qildi: name" → 'Ali'
proxy.age; // "O'qildi: age" → 25
new Proxy(target, handler):
- target — asl, "haqiqiy" obyekt
- handler — traplarni saqlovchi obyekt (har trap — ma'lum bir operatsiyani ushlab qoluvchi funksiya)
- proxy — bu yangi obyekt, target'ning o'zi emas
Tashqaridan qaraganda proxy xuddi target'dek ko'rinadi (typeof, iteratsiya, hammasi bir xil ishlaydi), lekin har bir amal avval handler ichidagi mos trap orqali "filtrlanadi".
4. Barcha traplar — to'liq ro'yxat va nima uchun kerak
Bu — Proxy'ning eng muhim qismi: u 13 xil fundamental operatsiyani ushlay oladi. Har biri — object bilan qilinadigan ma'lum bir "past darajadagi amal"ga mos keladi (bu amallar spec'da Internal Methods deb ataladi, masalan [[Get]], [[Set]], [[Delete]]).
| Trap | Qachon ishga tushadi | Misol |
|---|---|---|
get |
Property o'qilganda | proxy.x |
set |
Property yozilganda | proxy.x = 5 |
has |
in operatori |
'x' in proxy |
deleteProperty |
delete |
delete proxy.x |
ownKeys |
Object.keys(), for...in |
Object.keys(proxy) |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor() |
|
defineProperty |
Object.defineProperty() |
|
getPrototypeOf |
Object.getPrototypeOf(), __proto__ |
|
setPrototypeOf |
Object.setPrototypeOf() |
|
isExtensible |
Object.isExtensible() |
|
preventExtensions |
Object.preventExtensions() |
|
apply |
Funksiya sifatida chaqirilganda | proxy() |
construct |
new bilan chaqirilganda |
new proxy() |
5. get va set — eng ko'p ishlatiladigan traplar
const target = { name: 'Ali', age: 25 };
const validator = {
set(obj, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age raqam bo\'lishi kerak');
}
obj[prop] = value; // haqiqiy o'zgartirish shu yerda sodir bo'ladi
return true; // set trap MUHIM: true qaytarish shart (muvaffaqiyat belgisi)
},
get(obj, prop) {
if (!(prop in obj)) {
console.warn(`Ogohlantirish: '${prop}' mavjud emas`);
return undefined;
}
return obj[prop];
}
};
const user = new Proxy(target, validator);
user.age = 30; // OK
user.age = 'katta'; // TypeError: age raqam bo'lishi kerak
user.unknownProp; // Ogohlantirish: 'unknownProp' mavjud emas
Muhim qoida: set trap true (yoki truthy) qaytarishi shart — aks holda, strict modeda (va ES modullar har doim strict) TypeError tashlanadi. Bu — spec darajasidagi qat'iy talab, chunki [[Set]] internal metodi muvaffaqiyat/muvaffaqiyatsizlikni bildirishi kerak.
6. has trap — "yashirin" property yaratish
const hidden = new Proxy({ real: 1, _secret: 2 }, {
has(obj, prop) {
if (prop.startsWith('_')) return false; // _ bilan boshlanuvchilarni "yashiradi"
return prop in obj;
}
});
'real' in hidden; // true
'_secret' in hidden; // false — garchi haqiqatda mavjud bo'lsa ham!
Bu — Proxy'ning "haqiqat"ni butunlay o'zgartira olish kuchini ko'rsatadi: _secret haqiqatda target ichida bor, lekin in operatori orqali ko'rinmas qilib qo'yilgan.
7. apply va construct — funksiyani proxy qilish
Bu traplar faqat target funksiya bo'lgandagina ishlaydi:
function sum(a, b) { return a + b; }
const loggedSum = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`Chaqirildi: sum(${args.join(', ')})`);
return Reflect.apply(target, thisArg, args);
}
});
loggedSum(2, 3); // "Chaqirildi: sum(2, 3)" → 5
class User {
constructor(name) { this.name = name; }
}
const LoggedUser = new Proxy(User, {
construct(target, args) {
console.log(`Yangi instance: ${args[0]}`);
return Reflect.construct(target, args);
}
});
new LoggedUser('Ali'); // "Yangi instance: Ali" → User { name: 'Ali' }
Bu — masalan, funksiya chaqiruvlarini logging qilish, keshlash (memoization), ruxsatlarni tekshirish kabi vazifalar uchun juda foydali, chunki asl funksiyaning o'zini o'zgartirmasdan, uning atrofiga "nazorat qatlami" qo'yiladi.
8. Reflect nima — va nega Proxy bilan birga keladi
Reflect — bu ES6 bilan kiritilgan built-in obyekt, va u aynan Proxy trap nomlariga mos keluvchi statik metodlar to'plamidan iborat: Reflect.get(), Reflect.set(), Reflect.has(), Reflect.deleteProperty() va h.k.
Nega u kerak — bu savolning javobi eng muhim tushuncha: Proxy trap ichida siz odatda "default xatti-harakatni bajar" demoqchi bo'lasiz (masalan, faqat logging qo'shib, keyin asl amalni bajarish). Buni qo'lda yozish mumkin edi:
const handler = {
get(obj, prop) {
console.log('o\'qildi:', prop);
return obj[prop]; // qo'lda "default" ni takrorlash
}
};
Lekin bu yetarli emas — ayniqsa prototype chain, getter'lar ichidagi this kabi nozik holatlarda obj[prop] to'liq to'g'ri natija bermasligi mumkin. Reflect.get() esa aynan spec algoritmining o'zini ishga tushiradi — hech qanday farq yo'q:
const handler = {
get(obj, prop, receiver) {
console.log('o\'qildi:', prop);
return Reflect.get(obj, prop, receiver); // 100% to'g'ri, spec-mos default
}
};
Aniq muammoni ko'rsatuvchi misol — nega obj[prop] yetarli emas:
const target = {
_x: 10,
get x() { return this._x; }
};
const proxy = new Proxy(target, {
get(obj, prop, receiver) {
return obj[prop]; // NOTO'G'RI — receiver e'tiborsiz qoldirilgan
}
});
Bu yerda receiver — trap'ning uchinchi argumenti, u haqiqatda kim murojaat qilayotganini bildiradi (agar proxy biror boshqa obyektning prototype'i sifatida ishlatilsa, receiver — o'sha "asl" obyekt bo'ladi, target emas). obj[prop] bilan yozilsa, getter ichidagi this har doim obj (target) bo'lib qoladi, receiver emas — bu inheritance ishlatilganda noto'g'ri natijaga olib kelishi mumkin. Reflect.get(obj, prop, receiver) esa buni spec bo'yicha to'g'ri hal qiladi.
Xulosa: Reflect — Proxy trap'lar ichida "default xatti-harakatni to'g'ri chaqirish" uchun maxsus yaratilgan vosita. Ular hamkorlikda ishlash uchun juftlashtirilgan (Proxy trap nomlari va Reflect metod nomlari atayin bir xil).
9. Reflect — Proxy'siz ham foydali
Reflect faqat Proxy bilan ishlash uchun emas, u umuman object operatsiyalarini funksional (metod chaqiruvi) shaklda taqdim etadi — bu ba'zi operator ko'rinishidagi amallarni funksiya sifatida ishlatish imkonini beradi:
// Eski usul:
delete obj.x;
'x' in obj;
Object.getPrototypeOf(obj);
// Reflect bilan — funksiya sifatida, xato holatlarni yaxshiroq boshqaradi:
Reflect.deleteProperty(obj, 'x'); // true/false qaytaradi, operator emas
Reflect.has(obj, 'x'); // 'x' in obj bilan bir xil
Reflect.getPrototypeOf(obj); // Object.getPrototypeOf bilan deyarli bir xil
Muhim farq — Reflect.defineProperty vs Object.defineProperty: Object.defineProperty xato bo'lsa TypeError tashlaydi, Reflect.defineProperty esa xato bo'lsa shunchaki false qaytaradi — bu funksional, "istisno-siz" (exception-free) dasturlash uslubiga mos:
try {
Object.defineProperty(Object.freeze({}), 'x', { value: 1 }); // TypeError tashlaydi
} catch (e) { }
const success = Reflect.defineProperty(Object.freeze({}), 'x', { value: 1 });
console.log(success); // false — xato tashlanmadi, shunchaki false
10. V8 ichida Proxy qanday ishlaydi
Bu yerda tub tushunish kerak bo'lgan narsa: Proxy — V8'ning eng katta optimallashtirish mexanizmini (Hidden Class, Inline Cache) butunlay chetlab o'tadi.
Eslang — oddiy object'ga murojaat (obj.name) V8'da offset orqali, deyarli bepul ishlaydi (Hidden Class + Inline Cache tufayli, avval ko'rgan edik). Proxy bilan bu umuman boshqacha:
const proxy = new Proxy(target, handler);
proxy.name; // bu ENDI oddiy property lookup EMAS
proxy.name chaqirilganda, V8:
- proxy — maxsus internal
JSProxyturi ekanini aniqlaydi (Hidden Class mexanizmidan butunlay tashqarida) - handlerda
gettrap bormi tekshiradi - Bo'lsa — haqiqiy funksiya chaqiruvi sifatida
handler.get(target, 'name', proxy)ni bajaradi - Bo'lmasa — avtomatik ravishda target'ga default
[[Get]]algoritmini qo'llaydi (bu holatda tezroq, lekin baribir bir necha qo'shimcha tekshiruv bilan)
Bu degani — har bir proxy.anything murojaati doim kamida bitta qo'shimcha funksiya chaqiruvi (agar trap bo'lsa) yoki internal dispatch (trap bo'lmasa ham) orqali o'tadi. V8 buni hech qachon Inline Cache orqali "monomorphic fast path"ga aylantira olmaydi, chunki Proxy semantikasi bo'yicha handler har doim dinamik chaqirilishi kerak (JIT compiler buni oldindan "bashorat qilib" optimallashtira olmaydi — chunki handler mantig'i ixtiyoriy JS kodi, har xil bo'lishi mumkin).
Amaliy natija — performance farqi:
const plain = { x: 1 };
const proxied = new Proxy({ x: 1 }, { get: (t, p) => t[p] });
// Tsiklda million marta murojaat qilinsa:
for (let i = 0; i < 1e7; i++) plain.x; // juda tez — Inline Cache
for (let i = 0; i < 1e7; i++) proxied.x; // sezilarli sekinroq — har safar handler chaqiriladi
Bu farq odatda kod bazasi darajasida sezilmaydi (chunki Proxy odatda "hot path" — tez-tez chaqiriladigan kritik joylarda emas, balki validatsiya, logging, framework darajasida ishlatiladi), lekin performance-kritik tsikllarda Proxy ishlatishdan saqlanish kerak degan amaliy xulosa shundan kelib chiqadi.
11. Invariant'lar — Proxy cheksiz erkin emas
Proxy trap'lar to'liq erkin emas — spec ularga invariant (o'zgarmas qoida)lar qo'yadi, bu — engine'ning ba'zi ichki kafolatlarni saqlab qolishi uchun kerak. Masalan:
const target = Object.freeze({ x: 1 });
const proxy = new Proxy(target, {
get() { return 999; } // target.x=1 bo'lsa ham, boshqa qiymat qaytarmoqchi
});
proxy.x; // TypeError!
Chunki target freeze qilingan (configurable: false, writable: false property), spec talab qiladi: freeze qilingan, o'zgarmas property uchun get trap albatta target'dagi haqiqiy qiymatni qaytarishi shart. Aks holda — engine bu qoidabuzarlikni avtomatik tekshiradi va TypeError tashlaydi. Bu — Proxy "hamma narsani soxtalashtira olishi" haqidagi noto'g'ri tasavvurni to'g'irlaydi: u engine kafolatlarini buzolmaydi, faqat "buzilmaydigan" chegaralar ichida moslashuvchan.
12. Amaliy qo'llanilishi — real dunyoda qayerda ishlatiladi
1. Vue 3 Reactivity system — bu eng mashhur real misol. Vue 3 Proxy'ning get/set trap'lari orqali object'ning qaysi property'lari "kuzatilayotganini" (reactive dependency tracking) aniqlaydi:
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key); // "bu property o'qildi, kim bog'liq — eslab qol"
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
trigger(target, key); // "bu property o'zgardi, bog'liq narsalarni yangila"
return result;
}
});
}
Vue 2 buning uchun Object.defineProperty ishlatgan edi (faqat oldindan ma'lum property'lar uchun), Vue 3 Proxy'ga o'tgach — yangi qo'shilgan property'larni ham avtomatik kuzatish imkoniyatiga ega bo'ldi (bu — Vue 2'ning eng katta cheklovlaridan biri edi).
2. Validatsiya/schema tekshirish:
function createValidated(schema) {
return new Proxy({}, {
set(obj, prop, value) {
if (schema[prop] && typeof value !== schema[prop]) {
throw new TypeError(`${prop} ${schema[prop]} bo'lishi kerak`);
}
obj[prop] = value;
return true;
}
});
}
3. Default qiymatlar (masalan lug'atlar uchun):
const withDefault = (obj, defaultVal) => new Proxy(obj, {
get: (t, p) => p in t ? t[p] : defaultVal
});
const counts = withDefault({}, 0);
counts.apple; // 0 — hech qachon undefined emas
4. API "namespace" simulyatsiyasi, negative array indexing, va h.k. — bular ko'proq "creative" foydalanishlar, lekin printsip bir xil.
13. Proxy vs Object.defineProperty — yakuniy taqqoslash
| Object.defineProperty | Proxy | |
|---|---|---|
| Qamrov | Faqat e'lon qilingan property | Barcha operatsiyalar (13 trap) |
| Yangi property'lar | Avtomatik kuzatilmaydi | Avtomatik kuzatiladi (get/set trap har doim ishlaydi) |
| Performance | Tez (Hidden Class ichida) | Sekinroq (Inline Cache'dan tashqarida) |
| Array index/length | Qiyin, cheklangan | To'liq qo'llab-quvvatlaydi |
| delete, in, funksiya chaqiruvi | Yo'q | Bor (deleteProperty, has, apply) |
Xulosa
Proxy — object atrofida "qobiq" yaratib, undagi barcha 13 xil fundamental operatsiyani (get, set, has, deleteProperty, apply, construct va h.k.) ushlab qolish imkonini beruvchi meta-daraja mexanizmi — Object.definePropertydan farqli o'laroq, u faqat oldindan e'lon qilingan property bilan emas, balki har qanday, hatto hali mavjud bo'lmagan property bilan ham ishlaydi. Reflect — trap ichida "spec-mos default xatti-harakat"ni to'g'ri chaqirish uchun Proxy bilan juftlashtirilgan vosita, ayniqsa receiver argumentini to'g'ri uzatish orqali prototype chain va getter'lar ichidagi thisni to'g'ri saqlab qoladi. V8 darajasida Proxy Hidden Class va Inline Cache optimallashtirishini butunlay chetlab o'tadi — har bir murojaat handler orqali dinamik dispatch qiladi, shu sababli performance-kritik hot path'larda ishlatishdan saqlanish tavsiya etiladi. Proxy trap'lar cheksiz erkin emas — spec invariant'lari (masalan, freeze qilingan propertyni soxtalashtirib bo'lmasligi) orqali engine kafolatlari saqlanadi. Vue 3 reactivity tizimi — Proxy'ning eng mashhur amaliy qo'llanilishi, u orqali Vue 2'ning eng katta cheklovi (yangi property'larni avtomatik kuzata olmasligi) bartaraf etilgan.