JavaScript Proxy API'ni o'zlashtirish bo'yicha global dasturchilar uchun to'liq qo'llanma. Amaliy misollar, qo'llash holatlari va unumdorlik bo'yicha maslahatlar yordamida ob'ekt operatsiyalarini ushlab qolish va moslashtirishni o'rganing.
JavaScript Proxy API: Ob'ekt xulq-atvorini o'zgartirishni chuqur o'rganish
Zamonaviy JavaScript'ning rivojlanayotgan landshaftida dasturchilar doimiy ravishda ma'lumotlarni boshqarish va ular bilan ishlashning kuchliroq va oqlangan usullarini izlaydilar. Garchi sinflar, modullar va async/await kabi xususiyatlar kod yozish uslubimizni inqilob qilgan bo'lsa-da, ECMAScript 2015 (ES6) da kiritilgan, ko'pincha kam qo'llaniladigan kuchli metaprogrammalash xususiyati mavjud: Proxy API.
Metaprogrammalash qo'rqinchli eshitilishi mumkin, lekin bu shunchaki boshqa kod ustida ishlaydigan kod yozish tushunchasidir. Proxy API bu uchun JavaScript'ning asosiy vositasi bo'lib, u boshqa ob'ekt uchun "proksi" yaratishga imkon beradi, bu esa o'sha ob'ekt uchun fundamental operatsiyalarni ushlab qolishi va qayta belgilashi mumkin. Bu go'yo ob'ekt oldiga sozlanishi mumkin bo'lgan nazoratchi qo'yishga o'xshaydi va unga qanday kirish va uni o'zgartirish ustidan to'liq nazoratni beradi.
Ushbu keng qamrovli qo'llanma Proxy API'ni tushuntirib beradi. Biz uning asosiy tushunchalarini o'rganamiz, amaliy misollar bilan uning turli imkoniyatlarini tahlil qilamiz va ilg'or qo'llash holatlari hamda unumdorlik masalalarini muhokama qilamiz. Oxir-oqibat, siz Proxies nima uchun zamonaviy freymvorklarning asosiy toshi ekanligini va ulardan qanday qilib toza, kuchliroq va qo'llab-quvvatlanishi osonroq kod yozish uchun foydalanish mumkinligini tushunasiz.
Asosiy tushunchalarni tushunish: Maqsad, Ishlovchi va Qopqonlar
Proxy API uchta asosiy komponentga asoslangan. Ularning vazifalarini tushunish proksilarni o'zlashtirishning kalitidir.
- Maqsad (Target): Bu siz o'ramoqchi bo'lgan asl ob'ekt. U har qanday turdagi ob'ekt bo'lishi mumkin, jumladan, massivlar, funksiyalar yoki hatto boshqa proksi. Proksi bu maqsadni virtualizatsiya qiladi va barcha operatsiyalar oxir-oqibat (garchi shart bo'lmasa ham) unga yo'naltiriladi.
- Ishlovchi (Handler): Bu proksi uchun mantiqni o'z ichiga olgan ob'ekt. Bu xususiyatlari 'qopqonlar' deb nomlanuvchi funksiyalardan iborat bo'lgan o'rinbosar ob'ekt. Proksida biror operatsiya sodir bo'lganda, u ishlovchidan mos keladigan qopqonni qidiradi.
- Qopqonlar (Traps): Bular ishlovchidagi xususiyatlarga kirishni ta'minlaydigan metodlardir. Har bir qopqon fundamental ob'ekt operatsiyasiga mos keladi. Masalan,
get
qopqoni xususiyatni o'qishni,set
qopqoni esa xususiyatni yozishni ushlab qoladi. Agar ishlovchida qopqon belgilanmagan bo'lsa, operatsiya go'yo proksi yo'qdek maqsadga yo'naltiriladi.
Proksi yaratish sintaksisi juda oddiy:
const proxy = new Proxy(target, handler);
Keling, juda oddiy misolni ko'rib chiqaylik. Biz bo'sh ishlovchi yordamida barcha operatsiyalarni maqsad ob'ektiga shunchaki o'tkazib yuboradigan proksi yaratamiz.
// Asl ob'ekt
const target = {
message: "Hello, World!"
};
// Bo'sh ishlovchi. Barcha operatsiyalar maqsadga yo'naltiriladi.
const handler = {};
// Proksi ob'ekt
const proxy = new Proxy(target, handler);
// Proksi orqali xususiyatga kirish
console.log(proxy.message); // Natija: Hello, World!
// Operatsiya maqsadga yo'naltirildi
console.log(target.message); // Natija: Hello, World!
// Proksi orqali xususiyatni o'zgartirish
proxy.anotherMessage = "Hello, Proxy!";
console.log(proxy.anotherMessage); // Natija: Hello, Proxy!
console.log(target.anotherMessage); // Natija: Hello, Proxy!
Bu misolda proksi xuddi asl ob'ekt kabi ishlaydi. Haqiqiy kuch biz ishlovchida qopqonlarni belgilashni boshlaganimizda paydo bo'ladi.
Proksi anatomiyasi: Umumiy qopqonlarni o'rganish
Ishlovchi ob'ektida 13 tagacha turli xil qopqonlar bo'lishi mumkin, ularning har biri JavaScript ob'ektlarining fundamental ichki metodiga mos keladi. Keling, eng keng tarqalgan va foydali bo'lganlarini ko'rib chiqaylik.
Xususiyatlarga kirish qopqonlari
1. `get(target, property, receiver)`
Bu, ehtimol, eng ko'p ishlatiladigan qopqondir. U proksining biror xususiyati o'qilganda ishga tushadi.
target
: Asl ob'ekt.property
: Kirilayotgan xususiyat nomi.receiver
: Proksining o'zi yoki undan meros olgan ob'ekt.
Misol: Mavjud bo'lmagan xususiyatlar uchun standart qiymatlar.
const user = {
firstName: 'John',
lastName: 'Doe',
age: 30
};
const userHandler = {
get(target, property) {
// Agar xususiyat maqsadda mavjud bo'lsa, uni qaytaring.
// Aks holda, standart xabarni qaytaring.
return property in target ? target[property] : `Property '${property}' does not exist.`;
}
};
const userProxy = new Proxy(user, userHandler);
console.log(userProxy.firstName); // Natija: John
console.log(userProxy.age); // Natija: 30
console.log(userProxy.country); // Natija: Property 'country' does not exist.
2. `set(target, property, value, receiver)`
set
qopqoni proksining biror xususiyatiga qiymat tayinlanganda chaqiriladi. Bu validatsiya, jurnal yozish yoki faqat o'qish uchun mo'ljallangan ob'ektlar yaratish uchun juda mos keladi.
value
: Xususiyatga tayinlanayotgan yangi qiymat.- Qopqon mantiqiy qiymat qaytarishi kerak: tayinlash muvaffaqiyatli bo'lsa
true
, aks holdafalse
(bu qat'iy rejimdaTypeError
xatosiga olib keladi).
Misol: Ma'lumotlarni tekshirish (validatsiya).
const person = {
name: 'Jane Doe',
age: 25
};
const validationHandler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number' || !Number.isInteger(value)) {
throw new TypeError('Age must be an integer.');
}
if (value <= 0) {
throw new RangeError('Age must be a positive number.');
}
}
// Agar tekshiruvdan o'tsa, qiymatni maqsad ob'ektiga o'rnating.
target[property] = value;
// Muvaffaqiyatni bildiring.
return true;
}
};
const personProxy = new Proxy(person, validationHandler);
personProxy.age = 30; // Bu to'g'ri
console.log(personProxy.age); // Natija: 30
try {
personProxy.age = 'thirty'; // TypeError xatosini chiqaradi
} catch (e) {
console.error(e.message); // Natija: Age must be an integer.
}
try {
personProxy.age = -5; // RangeError xatosini chiqaradi
} catch (e) {
console.error(e.message); // Natija: Age must be a positive number.
}
3. `has(target, property)`
Ushbu qopqon in
operatorini ushlab qoladi. Bu sizga qaysi xususiyatlar ob'ektda mavjuddek ko'rinishini nazorat qilish imkonini beradi.
Misol: 'Shaxsiy' xususiyatlarni yashirish.
JavaScript'da umumiy qoida sifatida shaxsiy xususiyatlar oldiga pastki chiziqcha (_) qo'yiladi. Biz has
qopqonidan foydalanib, ularni in
operatoridan yashirishimiz mumkin.
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const hidingHandler = {
has(target, property) {
if (property.startsWith('_')) {
return false; // Mavjud emasdek ko'rsatish
}
return property in target;
}
};
const dataProxy = new Proxy(secretData, hidingHandler);
console.log('publicKey' in dataProxy); // Natija: true
console.log('_apiKey' in dataProxy); // Natija: false (garchi u maqsadda bo'lsa ham)
console.log('id' in dataProxy); // Natija: true
Eslatma: Bu faqat in
operatoriga ta'sir qiladi. Agar siz mos keladigan get
qopqonini ham amalga oshirmasangiz, dataProxy._apiKey
kabi to'g'ridan-to'g'ri kirish baribir ishlayveradi.
4. `deleteProperty(target, property)`
Bu qopqon delete
operatori yordamida biror xususiyat o'chirilganda ishga tushadi. Bu muhim xususiyatlarning o'chirilishini oldini olish uchun foydalidir.
Qopqon muvaffaqiyatli o'chirish uchun true
yoki muvaffaqiyatsiz bo'lsa false
qaytarishi kerak.
Misol: Xususiyatlarni o'chirishning oldini olish.
const immutableConfig = {
databaseUrl: 'prod.db.server',
port: 8080
};
const deletionGuardHandler = {
deleteProperty(target, property) {
if (property in target) {
console.warn(`Attempted to delete protected property: '${property}'. Operation denied.`);
return false;
}
return true; // Xususiyat shundoq ham mavjud emas edi
}
};
const configProxy = new Proxy(immutableConfig, deletionGuardHandler);
delete configProxy.port;
// Konsol natijasi: Attempted to delete protected property: 'port'. Operation denied.
console.log(configProxy.port); // Natija: 8080 (U o'chirilmadi)
Ob'ektni sanab chiqish va tavsiflash qopqonlari
5. `ownKeys(target)`
Bu qopqon Object.keys()
, Object.getOwnPropertyNames()
, Object.getOwnPropertySymbols()
va Reflect.ownKeys()
kabi ob'ektning o'z xususiyatlari ro'yxatini oladigan operatsiyalar tomonidan ishga tushiriladi.
Misol: Kalitlarni filtrlash.
Keling, buni oldingi 'shaxsiy' xususiyat misolimiz bilan birlashtirib, ularni to'liq yashiramiz.
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const keyHidingHandler = {
has(target, property) {
return !property.startsWith('_') && property in target;
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
},
get(target, property, receiver) {
// Shuningdek, to'g'ridan-to'g'ri kirishni oldini olish
if (property.startsWith('_')) {
return undefined;
}
return Reflect.get(target, property, receiver);
}
};
const fullProxy = new Proxy(secretData, keyHidingHandler);
console.log(Object.keys(fullProxy)); // Natija: ['publicKey', 'id']
console.log('publicKey' in fullProxy); // Natija: true
console.log('_apiKey' in fullProxy); // Natija: false
console.log(fullProxy._apiKey); // Natija: undefined
Bu yerda biz Reflect
'dan foydalanayotganimizga e'tibor bering. Reflect
ob'ekti ushlab qolinishi mumkin bo'lgan JavaScript operatsiyalari uchun metodlarni taqdim etadi va uning metodlari proksi qopqonlari bilan bir xil nom va imzolarga ega. Asl operatsiyani maqsadga yo'naltirish uchun Reflect
'dan foydalanish eng yaxshi amaliyotdir, bu standart xulq-atvorning to'g'ri saqlanishini ta'minlaydi.
Funksiya va konstruktor qopqonlari
Proksilar faqat oddiy ob'ektlar bilan cheklanmaydi. Maqsad funksiya bo'lganda, siz chaqiruvlar va konstruksiyalarni ushlab qolishingiz mumkin.
6. `apply(target, thisArg, argumentsList)`
Bu qopqon funksiya proksisi bajarilganda chaqiriladi. U funksiya chaqiruvini ushlab qoladi.
target
: Asl funksiya.thisArg
: Chaqiruv uchunthis
konteksti.argumentsList
: Funksiyaga uzatilgan argumentlar ro'yxati.
Misol: Funksiya chaqiruvlari va ularning argumentlarini jurnalga yozish.
function sum(a, b) {
return a + b;
}
const loggingHandler = {
apply(target, thisArg, argumentsList) {
console.log(`Calling function '${target.name}' with arguments: ${argumentsList}`);
// Asl funksiyani to'g'ri kontekst va argumentlar bilan bajaring
const result = Reflect.apply(target, thisArg, argumentsList);
console.log(`Function '${target.name}' returned: ${result}`);
return result;
}
};
const proxiedSum = new Proxy(sum, loggingHandler);
proxiedSum(5, 10);
// Konsol natijasi:
// Calling function 'sum' with arguments: 5,10
// Function 'sum' returned: 15
7. `construct(target, argumentsList, newTarget)`
Bu qopqon sinf yoki funksiya proksisida new
operatoridan foydalanishni ushlab qoladi.
Misol: Singleton naqshini amalga oshirish.
class MyDatabaseConnection {
constructor(url) {
this.url = url;
console.log(`Connecting to ${this.url}...`);
}
}
let instance;
const singletonHandler = {
construct(target, argumentsList) {
if (!instance) {
console.log('Creating new instance.');
instance = Reflect.construct(target, argumentsList);
}
console.log('Returning existing instance.');
return instance;
}
};
const ProxiedConnection = new Proxy(MyDatabaseConnection, singletonHandler);
const conn1 = new ProxiedConnection('db://primary');
// Konsol natijasi:
// Creating new instance.
// Connecting to db://primary...
// Returning existing instance.
const conn2 = new ProxiedConnection('db://secondary'); // URL e'tiborga olinmaydi
// Konsol natijasi:
// Returning existing instance.
console.log(conn1 === conn2); // Natija: true
console.log(conn1.url); // Natija: db://primary
console.log(conn2.url); // Natija: db://primary
Amaliy qo'llash holatlari va ilg'or naqshlar
Endi biz alohida qopqonlarni ko'rib chiqdik, keling ular real hayotdagi muammolarni hal qilish uchun qanday birlashtirilishi mumkinligini ko'rib chiqaylik.
1. API abstraksiyasi va ma'lumotlarni o'zgartirish
API'lar ko'pincha ma'lumotlarni ilovangiz qoidalariga mos kelmaydigan formatda qaytaradi (masalan, snake_case
vs camelCase
). Proksi bu konversiyani shaffof tarzda amalga oshirishi mumkin.
function snakeToCamel(s) {
return s.replace(/(_\w)/g, (m) => m[1].toUpperCase());
}
// Tasavvur qiling, bu API'dan olingan xom ma'lumotlarimiz
const apiResponse = {
user_id: 123,
first_name: 'Alice',
last_name: 'Wonderland',
account_status: 'active'
};
const camelCaseHandler = {
get(target, property) {
const camelCaseProperty = snakeToCamel(property);
// camelCase versiyasi to'g'ridan-to'g'ri mavjudligini tekshirish
if (camelCaseProperty in target) {
return target[camelCaseProperty];
}
// Asl xususiyat nomiga qaytish
if (property in target) {
return target[property];
}
return undefined;
}
};
const userModel = new Proxy(apiResponse, camelCaseHandler);
// Endi biz xususiyatlarga camelCase yordamida kira olamiz, garchi ular snake_case sifatida saqlangan bo'lsa ham
console.log(userModel.userId); // Natija: 123
console.log(userModel.firstName); // Natija: Alice
console.log(userModel.accountStatus); // Natija: active
2. Kuzatuvchilar va ma'lumotlarni bog'lash (Zamonaviy freymvorklarning yadrosi)
Proksilar Vue 3 kabi zamonaviy freymvorklardagi reaktivlik tizimlarining dvigatelidir. Proksilangan holat ob'ektidagi biror xususiyatni o'zgartirganingizda, set
qopqoni UI yoki ilovaning boshqa qismlarida yangilanishlarni ishga tushirish uchun ishlatilishi mumkin.
Mana juda soddalashtirilgan misol:
function createObservable(target, callback) {
const handler = {
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
callback(prop, value); // O'zgarish sodir bo'lganda qayta qo'ng'iroqni ishga tushirish
return result;
}
};
return new Proxy(target, handler);
}
const state = {
count: 0,
message: 'Hello'
};
function render(prop, value) {
console.log(`CHANGE DETECTED: The property '${prop}' was set to '${value}'. Re-rendering UI...`);
}
const observableState = createObservable(state, render);
observableState.count = 1;
// Konsol natijasi: CHANGE DETECTED: The property 'count' was set to '1'. Re-rendering UI...
observableState.message = 'Goodbye';
// Konsol natijasi: CHANGE DETECTED: The property 'message' was set to 'Goodbye'. Re-rendering UI...
3. Massivlarning manfiy indekslari
Klassik va qiziqarli misol - Python kabi tillarga o'xshash, -1
oxirgi elementga ishora qiladigan manfiy indekslarni qo'llab-quvvatlash uchun mahalliy massiv xulq-atvorini kengaytirish.
function createNegativeArrayProxy(arr) {
const handler = {
get(target, property) {
const index = Number(property);
if (!Number.isNaN(index) && index < 0) {
// Manfiy indeksni oxiridan boshlab musbatga o'tkazish
property = String(target.length + index);
}
return Reflect.get(target, property);
}
};
return new Proxy(arr, handler);
}
const originalArray = ['a', 'b', 'c', 'd', 'e'];
const proxiedArray = createNegativeArrayProxy(originalArray);
console.log(proxiedArray[0]); // Natija: a
console.log(proxiedArray[-1]); // Natija: e
console.log(proxiedArray[-2]); // Natija: d
console.log(proxiedArray.length); // Natija: 5
Unumdorlik masalalari va eng yaxshi amaliyotlar
Proksilar juda kuchli bo'lsa-da, ular sehrli tayoqcha emas. Ularning ta'sirini tushunish juda muhim.
Unumdorlikdagi qo'shimcha yuk
Proksi bilvosita qatlamni kiritadi. Proksilangan ob'ektdagi har bir operatsiya ishlovchidan o'tishi kerak, bu esa oddiy ob'ektdagi to'g'ridan-to'g'ri operatsiyaga nisbatan oz miqdorda qo'shimcha yuk qo'shadi. Ko'pgina ilovalar uchun (ma'lumotlarni tekshirish yoki freymvork darajasidagi reaktivlik kabi) bu yuk sezilarsiz. Biroq, unumdorlik muhim bo'lgan kodlarda, masalan, millionlab elementlarni qayta ishlaydigan zich tsiklda, bu to'siq bo'lishi mumkin. Agar unumdorlik asosiy masala bo'lsa, har doim benchmark o'tkazing.
Proksi invariantlari
Qopqon maqsad ob'ektning tabiati haqida to'liq yolg'on gapira olmaydi. JavaScript proksi qopqonlari rioya qilishi kerak bo'lgan 'invariantlar' deb nomlanuvchi qoidalar to'plamini amalga oshiradi. Invariantni buzish TypeError
'ga olib keladi.
Masalan, deleteProperty
qopqoni uchun invariant shundan iboratki, agar maqsad ob'ektidagi mos keladigan xususiyat sozlanmaydigan (non-configurable) bo'lsa, u true
(muvaffaqiyatni bildiruvchi) qaytara olmaydi. Bu proksining o'chirib bo'lmaydigan xususiyatni o'chirganligini da'vo qilishining oldini oladi.
const target = {};
Object.defineProperty(target, 'unbreakable', { value: 10, configurable: false });
const handler = {
deleteProperty(target, prop) {
// Bu invariantni buzadi
return true;
}
};
const proxy = new Proxy(target, handler);
try {
delete proxy.unbreakable; // Bu xatolikni keltirib chiqaradi
} catch (e) {
console.error(e.message);
// Natija: 'deleteProperty' on proxy: returned true for non-configurable property 'unbreakable'
}
Proksilarni qachon ishlatish kerak (va qachon ishlatmaslik kerak)
- Quyidagilar uchun yaxshi: Freymvorklar va kutubxonalar yaratish (masalan, holatni boshqarish, ORMlar), nosozliklarni tuzatish va jurnal yozish, mustahkam validatsiya tizimlarini amalga oshirish va asosiy ma'lumotlar tuzilmalarini abstrakt qiluvchi kuchli API'lar yaratish.
- Quyidagilar uchun alternativalarni ko'rib chiqing: Unumdorlik muhim bo'lgan algoritmlar, sinf yoki zavod funksiyasi yetarli bo'lgan oddiy ob'ekt kengaytmalari yoki ES6 qo'llab-quvvatlamaydigan juda eski brauzerlarni qo'llab-quvvatlash kerak bo'lganda.
Bekor qilinadigan proksilar
Proksini 'o'chirib qo'yish' kerak bo'lishi mumkin bo'lgan holatlar uchun (masalan, xavfsizlik sabablari yoki xotirani boshqarish uchun), JavaScript Proxy.revocable()
'ni taqdim etadi. U proksi va revoke
funksiyasini o'z ichiga olgan ob'ektni qaytaradi.
const target = { data: 'sensitive' };
const handler = {};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.data); // Natija: sensitive
// Endi biz proksining kirish huquqini bekor qilamiz
revoke();
try {
console.log(proxy.data); // Bu xatolikni keltirib chiqaradi
} catch (e) {
console.error(e.message);
// Natija: Cannot perform 'get' on a proxy that has been revoked
}
Proksilar va boshqa metaprogrammalash usullari
Proksilardan oldin dasturchilar shunga o'xshash maqsadlarga erishish uchun boshqa usullardan foydalanishgan. Proksilar qanday taqqoslanishini tushunish foydalidir.
`Object.defineProperty()`
Object.defineProperty()
ma'lum xususiyatlar uchun getter va setterlarni belgilash orqali ob'ektni to'g'ridan-to'g'ri o'zgartiradi. Proksilar esa, aksincha, asl ob'ektni umuman o'zgartirmaydi; ular uni o'rab oladi.
- Qamrov doirasi: `defineProperty` har bir xususiyat asosida ishlaydi. Kuzatmoqchi bo'lgan har bir xususiyat uchun getter/setter belgilashingiz kerak. Proksining
get
vaset
qopqonlari global bo'lib, har qanday xususiyatdagi operatsiyalarni, shu jumladan keyinchalik qo'shilgan yangilarini ham ushlab qoladi. - Imkoniyatlar: Proksilar `deleteProperty`,
in
operatori va funksiya chaqiruvlari kabi `defineProperty` qila olmaydigan kengroq operatsiyalarni ushlab qolishi mumkin.
Xulosa: Virtualizatsiya kuchi
JavaScript Proxy API shunchaki aqlli xususiyat emas; bu bizning ob'ektlarni loyihalash va ular bilan ishlash uslubimizdagi fundamental o'zgarishdir. Fundamental operatsiyalarni ushlab qolish va moslashtirish imkonini berish orqali, Proksilar kuchli naqshlar dunyosiga eshik ochadi: uzluksiz ma'lumotlarni tekshirish va o'zgartirishdan tortib, zamonaviy foydalanuvchi interfeyslarini quvvatlantiradigan reaktiv tizimlargacha.
Garchi ular kichik unumdorlik xarajati va rioya qilish kerak bo'lgan qoidalar to'plami bilan birga kelsa-da, ularning toza, bog'liqlikdan xoli va kuchli abstraksiyalar yaratish qobiliyati tengsizdir. Ob'ektlarni virtualizatsiya qilish orqali siz yanada mustahkam, qo'llab-quvvatlanishi oson va ifodali tizimlar qurishingiz mumkin. Keyingi safar ma'lumotlarni boshqarish, validatsiya yoki kuzatuvchanlik bilan bog'liq murakkab muammoga duch kelganingizda, Proksi bu ish uchun to'g'ri vosita ekanligini o'ylab ko'ring. Bu sizning asboblar to'plamingizdagi eng oqlangan yechim bo'lishi mumkin.