Proxy objekti boshqa objektni oârab oladi va xususiyatlarni oâqish/yozish va boshqa amallarni tutib qoladi, ixtiyoriy ravishda ularni oâzi hal qiladi yoki objektga shaffof ravishda hal qilishga imkon beradi.
Proxylar koâplab kutubxonalar va baâzi brauzer freymvorklarida ishlatiladi. Bu maqolada koâplab amaliy dasturlarni koâramiz.
Proxy
Sintaksis:
let proxy = new Proxy(target, handler)
targetâ oâraladigan objekt, funktsiyalar ham boâlishi mumkin.handlerâ proxy konfiguratsiyasi: amallarni tutib qoladigan "trap"lar, metodlar bilan objekt. â masalantargetning xususiyatini oâqish uchungettrap,targetga xususiyat yozish uchunsettrap va hokazo.
proxy dagi amallar uchun, agar handler da tegishli trap boâlsa, u ishlaydi va proxy uni hal qilish imkoniyatiga ega boâladi, aks holda amal target da bajariladi.
Boshlangâich misol sifatida, hech qanday traplarsiz proxy yarataylik:
let target = {};
let proxy = new Proxy(target, {}); // bo'sh handler
proxy.test = 5; // proxyga yozish (1)
alert(target.test); // 5, xususiyat targetda paydo bo'ldi!
alert(proxy.test); // 5, uni proxydan ham o'qiy olamiz (2)
for(let key in proxy) alert(key); // test, iteratsiya ishlaydi (3)
Traplar yoâqligi sababli, proxy dagi barcha amallar target ga yoânaltiriladi.
proxy.test=yozish amalitargetda qiymat oârnatadi.proxy.testoâqish amalitargetdan qiymat qaytaradi.proxyboâylab iteratsiyatargetdan qiymatlar qaytaradi.
Koârib turganingizdek, hech qanday traplarsiz, proxy target atrofida shaffof wrapper.
Proxy maxsus âekzotik objektâ. Uning oâz xususiyatlari yoâq. Boâsh handler bilan u amallarni shaffof ravishda target ga yoânaltiradi.
Koâproq imkoniyatlarni faollashtirish uchun traplar qoâshaylik.
Ular bilan nimani tutib qolishimiz mumkin?
Objektlardagi koâpchilik amallar uchun JavaScript spetsifikatsiyasida eng past darajada qanday ishlashini tavsiflovchi âichki metodâ mavjud. Masalan [[Get]], xususiyatni oâqish uchun ichki metod, [[Set]], xususiyatni yozish uchun ichki metod va hokazo. Bu metodlar faqat spetsifikatsiyada ishlatiladi, biz ularni nom bilan toâgâridan-toâgâri chaqira olmaymiz.
Proxy traplar bu metodlarning chaqiruvlarini tutib qoladi. Ular Proxy spetsifikatsiyasi va quyidagi jadvalda koârsatilgan.
Har bir ichki metod uchun ushbu jadvalda trap mavjud: amalni tutib qolish uchun new Proxy ning handler parametriga qoâshishimiz mumkin boâlgan metod nomi:
| Ichki metod | Handler metodi | Qachon ishga tushadi⦠|
|---|---|---|
[[Get]] |
get |
xususiyatni oâqish |
[[Set]] |
set |
xususiyatga yozish |
[[HasProperty]] |
has |
in operatori |
[[Delete]] |
deleteProperty |
delete operatori |
[[Call]] |
apply |
funktsiya chaqiruvi |
[[Construct]] |
construct |
new operatori |
[[GetPrototypeOf]] |
getPrototypeOf |
Object.getPrototypeOf |
[[SetPrototypeOf]] |
setPrototypeOf |
Object.setPrototypeOf |
[[IsExtensible]] |
isExtensible |
Object.isExtensible |
[[PreventExtensions]] |
preventExtensions |
Object.preventExtensions |
[[DefineOwnProperty]] |
defineProperty |
Object.defineProperty, Object.defineProperties |
[[GetOwnProperty]] |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries |
[[OwnPropertyKeys]] |
ownKeys |
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object.keys/values/entries |
JavaScript baâzi invariantlarni taâminlaydi â ichki metodlar va traplar tomonidan bajarilishi kerak boâlgan shartlar.
Ularning aksariyati qaytariladigan qiymatlar uchun:
- Agar qiymat muvaffaqiyatli yozilgan boâlsa
[[Set]]trueqaytarishi kerak, aks holdafalse. - Agar qiymat muvaffaqiyatli oâchirilgan boâlsa
[[Delete]]trueqaytarishi kerak, aks holdafalse. - â¦va hokazo, quyidagi misollarda koâproq koâramiz.
Boshqa baâzi invariantlar ham bor, masalan:
- Proxy objektiga qoâllanilgan
[[GetPrototypeOf]]proxy objektining target objektiga qoâllanilgan[[GetPrototypeOf]]bilan bir xil qiymat qaytarishi kerak. Boshqacha qilib aytganda, proxyning prototype ini oâqish har doim target objektning prototype ini qaytarishi kerak.
Traplar bu amallarni tutib qolishi mumkin, lekin bu qoidalarga rioya qilishi kerak.
Invariantlar til xususiyatlarining toâgâri va izchil xatti-harakatini taâminlaydi. Toâliq invariantlar roâyxati spetsifikatsiyada. Agar gâalati narsa qilmayotgan boâlsangiz, ularni buzmasligingiz mumkin.
Buni amaliy misollarda qanday ishlashini koâraylik.
âgetâ trap bilan standart qiymat
Eng keng tarqalgan traplar xususiyatlarni oâqish/yozish uchun.
Oâqishni tutib qolish uchun handler da get(target, property, receiver) metodi boâlishi kerak.
U xususiyat oâqilganda quyidagi argumentlar bilan ishga tushadi:
targetâ maqsadli objekt,new Proxyga birinchi argument sifatida uzatilgan,propertyâ xususiyat nomi,receiverâ agar target xususiyati getter boâlsa, u holdareceiveruning chaqiruvidathissifatida ishlatilishi kerak boâlgan objekt. Odatda buproxyobjektining oâzi (yoki agar proxydan meros olsak, undan meros oluvchi objekt). Hozir bizga bu argument kerak emas, shuning uchun u keyinroq batafsil tushuntiriladi.
Objekt uchun standart qiymatlarni amalga oshirish uchun get dan foydalanamiz.
Mavjud boâlmagan qiymatlar uchun 0 qaytaradigan raqamli massiv yaratamiz.
Odatda mavjud boâlmagan massiv elementini olishga harakat qilganda, undefined olinadi, lekin biz oddiy massivni oâqishni tutib qoladigan va bunday xususiyat boâlmasa 0 qaytaradigan proxyga oâraymiz:
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
get(target, prop) {
if (prop in target) {
return target[prop];
} else {
return 0; // standart qiymat
}
}
});
alert( numbers[1] ); // 1
alert( numbers[123] ); // 0 (bunday element yo'q)
Koârib turganingizdek, get trap bilan buni qilish juda oson.
âStandartâ qiymatlar uchun har qanday mantiqni amalga oshirish uchun Proxy dan foydalanishimiz mumkin.
Iboralar va ularning tarjimalari bilan lugâat borligini tasavvur qiling:
let dictionary = {
'Hello': 'Hola',
'Bye': 'Adiós'
};
alert( dictionary['Hello'] ); // Hola
alert( dictionary['Welcome'] ); // undefined
Hozir, agar ibora boâlmasa, dictionary dan oâqish undefined qaytaradi. Lekin amalda, iborani tarjima qilmaslik odatda undefined dan yaxshiroq. Shuning uchun bu holda undefined oârniga tarjima qilinmagan iborani qaytaraylik.
Bunga erishish uchun biz dictionary ni oâqish amallarini tutib qoladigan proxyga oâraymiz:
let dictionary = {
'Hello': 'Hola',
'Bye': 'Adiós'
};
dictionary = new Proxy(dictionary, {
get(target, phrase) { // lug'atdan xususiyat o'qishni tutib qolish
if (phrase in target) { // agar lug'atda bo'lsa
return target[phrase]; // tarjimani qaytarish
} else {
// aks holda, tarjima qilinmagan iborani qaytarish
return phrase;
}
}
});
// Lug'atdan ixtiyoriy iboralarni qidiring!
// Eng yomoni, ular tarjima qilinmaydi.
alert( dictionary['Hello'] ); // Hola
alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (tarjima yo'q)
Proxy oâzgaruvchini qanday qayta yozishiga eâtibor bering:
dictionary = new Proxy(dictionary, ...);
Proxy hamma joyda target objektni butunlay almashtirishi kerak. Proxy qilingandan keyin hech kim target objektga murojaat qilmasligi kerak. Aks holda chalkashlik oson.
âsetâ trap bilan validatsiya
Faqat raqamlar uchun massiv kerak deylik. Agar boshqa turdagi qiymat qoâshilsa, xato boâlishi kerak.
set trap xususiyat yozilganda ishga tushadi.
set(target, property, value, receiver):
targetâ maqsadli objekt,new Proxyga birinchi argument sifatida uzatilgan,propertyâ xususiyat nomi,valueâ xususiyat qiymati,receiverâgettrap ga oâxshash, faqat setter xususiyatlari uchun muhim.
set trap oârnatish muvaffaqiyatli boâlsa true, aks holda false qaytarishi kerak (TypeError ni ishga tushiradi).
Yangi qiymatlarni validatsiya qilish uchun undan foydalanamiz:
let numbers = [];
numbers = new Proxy(numbers, { // (*)
set(target, prop, val) { // xususiyat yozishni tutib qolish uchun
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
numbers.push(1); // muvaffaqiyatli qo'shildi
numbers.push(2); // muvaffaqiyatli qo'shildi
alert("Uzunlik: " + numbers.length); // 2
numbers.push("test"); // TypeError ('set' on proxy returned false)
alert("Bu qator hech qachon yetib bormaydi (yuqoridagi qatorda xato)");
Eâtibor bering: massivlarning oârnatilgan funksionalligi hali ham ishlaydi! Qiymatlar push orqali qoâshiladi. length xususiyati qiymatlar qoâshilganda avtomatik ravishda oshadi. Bizning proxy hech narsani buzmaydi.
push va unshift kabi qiymat qoâshuvchi massiv metodlarini qayta yozish va ularga tekshiruv qoâshish kerak emas, chunki ular ichki tomondan proxy tomonidan tutib qolinadigan [[Set]] amalidan foydalanadi.
Shuning uchun kod toza va ixcham.
true qaytarishni unutmangYuqorida aytilganidek, saqlanishi kerak boâlgan invariantlar bor.
set uchun, muvaffaqiyatli yozish uchun true qaytarishi kerak.
Agar buni qilishni unutsak yoki biron falsy qiymat qaytarsak, amal TypeError ni ishga tushiradi.
âownKeysâ va âgetOwnPropertyDescriptorâ bilan iteratsiya
Object.keys, for..in sikl va objekt xususiyatlari boâylab iteratsiya qiladigan koâpchilik boshqa metodlar xususiyatlar roâyxatini olish uchun [[OwnPropertyKeys]] ichki metodidan (uni ownKeys trap tutib qoladi) foydalanadi.
Bunday metodlar tafsilotlarda farqlanadi:
Object.getOwnPropertyNames(obj)simvol boâlmagan kalitlarni qaytaradi.Object.getOwnPropertySymbols(obj)simvol kalitlarni qaytaradi.Object.keys/values()enumerablebayroq bilan simvol boâlmagan kalitlar/qiymatlarni qaytaradi (xususiyat bayroqlari Xususiyat bayroqlari va tavsiflovchilar maqolasida tushuntirilgan).for..inenumerablebayroq bilan simvol boâlmagan kalitlar va shuningdek prototype kalitlari boâylab sikl qiladi.
â¦Lekin bularning barchasi shu roâyxat bilan boshlanadi.
Quyidagi misolda biz pastki chiziq _ bilan boshlanadigan xususiyatlarni oâtkazib yuborish uchun user boâylab for..in sikl, shuningdek Object.keys va Object.values ni qilish uchun ownKeys trap dan foydalanamiz:
let user = {
name: "John",
age: 30,
_password: "***"
};
user = new Proxy(user, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
// "ownKeys" _password ni filtrlab tashlaydi
for(let key in user) alert(key); // name, keyin: age
// bu metodlarga ham bir xil ta'sir:
alert( Object.keys(user) ); // name,age
alert( Object.values(user) ); // John,30
Hozirgacha ishlaydi.
Garchi, agar biz objektda mavjud boâlmagan kalitni qaytarsak, Object.keys uni roâyxatga olmaydi:
let user = { };
user = new Proxy(user, {
ownKeys(target) {
return ['a', 'b', 'c'];
}
});
alert( Object.keys(user) ); // <bo'sh>
Nima uchun? Sababi oddiy: Object.keys faqat enumerable bayroqli xususiyatlarni qaytaradi. Buni tekshirish uchun u har bir xususiyat uchun uning descriptorini olish uchun ichki [[GetOwnProperty]] metodini chaqiradi. Va bu yerda, xususiyat yoâq boâlganligi sababli, uning descriptori boâsh, enumerable bayroq yoâq, shuning uchun u oâtkazib yuboriladi.
Object.keys ning xususiyatni qaytarishi uchun, u objektda enumerable bayroq bilan mavjud boâlishi yoki biz [[GetOwnProperty]] ga chaqiruvlarni tutib qolishimiz (getOwnPropertyDescriptor trap buni qiladi) va enumerable: true bilan descriptor qaytarishimiz mumkin.
Mana buning misoli:
let user = { };
user = new Proxy(user, {
ownKeys(target) { // xususiyatlar ro'yxatini olish uchun bir marta chaqiriladi
return ['a', 'b', 'c'];
},
getOwnPropertyDescriptor(target, prop) { // har bir xususiyat uchun chaqiriladi
return {
enumerable: true,
configurable: true
/* ...boshqa bayroqlar, ehtimol "value:..." */
};
}
});
alert( Object.keys(user) ); // a, b, c
Yana bir marta eslatib oâtaylik: biz faqat xususiyat objektda yoâq boâlsa [[GetOwnProperty]] ni tutib qolishimiz kerak.
âdeletePropertyâ va boshqa traplar bilan himoyalangan xususiyatlar
Pastki chiziq _ prefiksi bilan xususiyatlar va metodlar ichki degan keng tarqalgan konventsiya mavjud. Ularga objekt tashqarisidan kirilmasligi kerak.
Biroq texnik jihatdan bu mumkin:
let user = {
name: "John",
_password: "secret"
};
alert(user._password); // secret
_ bilan boshlanadigan xususiyatlarga har qanday kirishni oldini olish uchun proxylardan foydalanamiz.
Bizga traplar kerak boâladi:
getbunday xususiyatni oâqishda xato tashlash uchun,setyozishda xato tashlash uchun,deletePropertyoâchirishda xato tashlash uchun,ownKeysfor..invaObject.keyskabi metodlardan_bilan boshlanadigan xususiyatlarni istisno qilish uchun.
Mana kod:
let user = {
name: "John",
_password: "***"
};
user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error("Kirish taqiqlangan");
}
let value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value; // (*)
},
set(target, prop, val) { // xususiyat yozishni tutib qolish uchun
if (prop.startsWith('_')) {
throw new Error("Kirish taqiqlangan");
} else {
target[prop] = val;
return true;
}
},
deleteProperty(target, prop) { // xususiyat o'chirishni tutib qolish uchun
if (prop.startsWith('_')) {
throw new Error("Kirish taqiqlangan");
} else {
delete target[prop];
return true;
}
},
ownKeys(target) { // xususiyatlar ro'yxatini tutib qolish uchun
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
// "get" _password o'qishga ruxsat bermaydi
try {
alert(user._password); // Xato: Kirish taqiqlangan
} catch(e) { alert(e.message); }
// "set" _password yozishga ruxsat bermaydi
try {
user._password = "test"; // Xato: Kirish taqiqlangan
} catch(e) { alert(e.message); }
// "deleteProperty" _password o'chirishga ruxsat bermaydi
try {
delete user._password; // Xato: Kirish taqiqlangan
} catch(e) { alert(e.message); }
// "ownKeys" _password ni filtrlab tashlaydi
for(let key in user) alert(key); // name
(*) qatoridagi get trap dagi muhim tafsilotga eâtibor bering:
get(target, prop) {
// ...
let value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value; // (*)
}
Nima uchun funktsiyani value.bind(target) chaqirish kerak?
Sababi shundaki, user.checkPassword() kabi objekt metodlari _password ga kira olishi kerak:
user = {
// ...
checkPassword(value) {
// objekt metodi _password o'qiy olishi kerak
return value === this._password;
}
}
user.checkPassword() ga chaqiruv proxy qilingan user ni this sifatida oladi (nuqta oldidagi objekt this ga aylanadi), shuning uchun u this._password ga kirishga harakat qilganda, get trap faollashadi (u har qanday xususiyat oâqishda ishga tushadi) va xato tashlaydi.
Shuning uchun biz objekt metodlarining kontekstini (*) qatorida asl objekt target ga bogâlaymiz. Keyin ularning kelajakdagi chaqiruvlari hech qanday traplarsiz target ni this sifatida ishlatadi.
Bu yechim odatda ishlaydi, lekin ideal emas, chunki metod proxy qilinmagan objektni boshqa joyga uzatishi mumkin va keyin biz chalkashib qolamiz: asl objekt qayerda va proxy qilingan qayerda?
Bundan tashqari, objekt bir necha marta proxy qilinishi mumkin (bir nechta proxylar objektga turli âoâzgarishlarâ qoâshishi mumkin), va agar biz metodga oâralmagan objektni uzatsak, kutilmagan oqibatlar boâlishi mumkin.
Shuning uchun bunday proxy hamma joyda ishlatilmasligi kerak.
Zamonaviy JavaScript mexanizmlari klasslarda # prefiksi bilan private xususiyatlarni mahalliy qoâllab-quvvatlaydi. Ular Xususiy va himoyalangan xususiyatlar va usullar maqolasida tasvirlangan. Proxylar kerak emas.
Biroq bunday xususiyatlarning oâz muammolari bor. Xususan, ular meros boâlib oâtmaydi.
âhasâ trap bilan âDiapazon ichidaâ
Koâproq misollar koâraylik.
Bizda range objekti bor:
let range = {
start: 1,
end: 10
};
Raqam range da ekanligini tekshirish uchun in operatoridan foydalanishni xohlaymiz.
has trap in chaqiruvlarini tutib qoladi.
has(target, property)
targetâ maqsadli objekt,new Proxyga birinchi argument sifatida uzatilgan,propertyâ xususiyat nomi
Mana demo:
let range = {
start: 1,
end: 10
};
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end;
}
});
alert(5 in range); // true
alert(50 in range); // false
Chiroyli sintaktik shakar, shunday emasmi? Va amalga oshirish juda oddiy.
Funktsiyalarni o'rash: "apply"
Biz proxy ni funktsiya atrofida ham oârashimiz mumkin.
apply(target, thisArg, args) trap proxy ni funktsiya sifatida chaqirishni boshqaradi:
targetmaqsadli objekt (JavaScript da funktsiya objekt),thisArgthisning qiymati.argsargumentlar roâyxati.
Masalan, Dekorativlar va ekspeditorlik, call/apply maqolasida qilgan delay(f, ms) dekoratorni eslaymiz.
Oâsha maqolada biz buni proxylardan foydalanmasdan qildik. delay(f, ms) ga chaqiruv ms millisekunddan keyin barcha chaqiruvlarni f ga yoânaltiradigan funktsiya qaytardi.
Mana oldingi, funktsiyaga asoslangan amalga oshirish:
function delay(f, ms) {
// timeout dan keyin chaqiruvni f ga o'tkazadigan wrapper qaytarish
return function() { // (*)
setTimeout(() => f.apply(this, arguments), ms);
};
}
function sayHi(user) {
alert(`Salom, ${user}!`);
}
// bu o'rashdan keyin sayHi chaqiruvlari 3 soniyaga kechiktiriladi
sayHi = delay(sayHi, 3000);
sayHi("John"); // Salom, John! (3 soniyadan keyin)
Natija bir xil, lekin endi nafaqat chaqiruvlar, balki proxy dagi barcha amallar asl funktsiyaga yoânaltiriladi. Shuning uchun sayHi.length (*) qatorida oârashdan keyin toâgâri qaytariladi.
Biz âboyroqâ wrapper oldik.
Boshqa traplar ham mavjud: toâliq roâyxat ushbu maqola boshida. Ulardan foydalanish namunasi yuqoridagiga oâxshash.
Reflect
Reflect â bu Proxy yaratishni soddalashtiruvchi oârnatilgan objekt.
Ilgari aytilganidek, [[Get]], [[Set]] va boshqalar kabi ichki metodlar faqat spetsifikatsiya uchun, ularni toâgâridan-toâgâri chaqirish mumkin emas.
Reflect objekti buni biroz mumkin qiladi. Uning metodlari ichki metodlar atrofidagi minimal wrapperlar.
Mana bir xil ishni qiladigan amallar va Reflect chaqiruvlari misollari:
| Amal | Reflect chaqiruvi |
Ichki metod |
|---|---|---|
obj[prop] |
Reflect.get(obj, prop) |
[[Get]] |
obj[prop] = value |
Reflect.set(obj, prop, value) |
[[Set]] |
delete obj[prop] |
Reflect.deleteProperty(obj, prop) |
[[Delete]] |
new F(value) |
Reflect.construct(F, value) |
[[Construct]] |
| ⦠| ⦠| ⦠|
Masalan:
let user = {};
Reflect.set(user, 'name', 'John');
alert(user.name); // John
Xususan, Reflect bizga operatorlarni (new, deleteâ¦) funktsiyalar (Reflect.construct, Reflect.deleteProperty, â¦) sifatida chaqirish imkonini beradi. Bu qiziq imkoniyat, lekin bu yerda boshqa narsa muhim.
Proxy tomonidan tutib qolinadigan har bir ichki metod uchun Reflect da Proxy trap bilan bir xil nom va argumentlarga ega mos metod mavjud.
Shunday qilib biz amallarni asl objektga yoânaltirish uchun Reflect dan foydalanishimiz mumkin.
Ushbu misolda ikkala trap get va set shaffof ravishda (goâyo ular mavjud emas) oâqish/yozish amallarini objektga yoânaltiradi, xabar koârsatadi:
let user = {
name: "John",
};
user = new Proxy(user, {
get(target, prop, receiver) {
alert(`GET ${prop}`);
return Reflect.get(target, prop, receiver); // (1)
},
set(target, prop, val, receiver) {
alert(`SET ${prop}=${val}`);
return Reflect.set(target, prop, val, receiver); // (2)
}
});
let name = user.name; // "GET name" ni ko'rsatadi
user.name = "Pete"; // "SET name=Pete" ni ko'rsatadi
Bu yerda:
Reflect.getobjekt xususiyatini oâqiydi.Reflect.setobjekt xususiyatini yozadi va muvaffaqiyatli boâlsatrue, aks holdafalseqaytaradi.
Yaâni, hamma narsa oddiy: agar trap chaqiruvni objektga yoânaltirmoqchi boâlsa, bir xil argumentlar bilan Reflect.<method> ni chaqirish yetarli.
Koâp holatlarda biz Reflect siz bir xilini qila olamiz, masalan, xususiyatni oâqish Reflect.get(target, prop, receiver) ni target[prop] bilan almashtirish mumkin. Biroq muhim nozikliklar bor.
Getter ni proxy qilish
Keling, Reflect.get nima uchun yaxshiroq ekanligini koârsatuvchi misolni koâraylik. Va biz shuningdek get/set ning uchinchi receiver argumenti nima uchun kerakligini koâramiz, avval ishlatmagan.
Bizda _name xususiyati va uning uchun getter bilan user objekti bor.
Mana uni atrofidagi proxy:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop];
}
});
alert(userProxy.name); // Guest
Bu yerda get trap âshaffofâ, u asl xususiyatni qaytaradi va boshqa hech narsa qilmaydi. Bu bizning misolimiz uchun yetarli.
Hammasi toâgâri koârinadi. Lekin misolni biroz murakkabroq qilaylik.
user dan boshqa admin objektini meros qilib olgandan keyin, notoâgâri xatti-harakatni kuzatishimiz mumkin:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]; // (*) target = user
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
// Kutilgan: Admin
alert(admin.name); // chiqadi: Guest (?!?)
admin.name oâqish âAdminâ ni qaytarishi kerak edi, âGuestâ ni emas!
Nima gap? Ehtimol meros bilan biror narsa notoâgâri qildik?
Lekin agar proxy ni olib tashlasak, hamma narsa kutilgandek ishlaydi.
Aslida muammo proxy da, (*) qatorida.
-
admin.nameni oâqiganimizda,adminobjektida bunday oâz xususiyat yoâq boâlganligi sababli, qidiruv uning prototype ga boradi. -
Prototype
userProxydir. -
Proxy dan
namexususiyatini oâqiganda, uninggettrap ishga tushadi va uni asl objektdan(*)qatoridatarget[prop]sifatida qaytaradi.target[prop]ga chaqiruv,propgetter boâlganda, oâz kodinithis=targetkontekstida ishga tushiradi. Shuning uchun natija asl objekttarget, yaâni:userdanthis._name.
Bunday vaziyatlarni tuzatish uchun bizga get trap ning uchinchi argumenti receiver kerak. U getter ga uzatiladigan toâgâri this ni saqlaydi. Bizning holatimizda bu admin.
Getter uchun kontekstni qanday uzatish mumkin? Oddiy funktsiya uchun call/apply dan foydalanishimiz mumkin, lekin bu getter, u âchaqirilmaydiâ, faqat kiriladi.
Reflect.get buni qila oladi. Agar uni ishlatsak hamma narsa toâgâri boâladi.
Mana tuzatilgan variant:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); // (*)
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
alert(admin.name); // Admin
Endi toâgâri this ga (yaâni admin ga) havola saqlaydigan receiver (*) qatorida Reflect.get yordamida getter ga uzatiladi.
Biz trap ni yanada qisqa yoza olamiz:
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
Reflect chaqiruvlari traplar bilan aynan bir xil nomlanadi va bir xil argumentlarni qabul qiladi. Ular aynan shu tarzda moâljallangan.
Shuning uchun, return Reflect... amallarni yoânaltirish uchun xavfsiz va hech narsani unutmasligimizni taâminlovchi usul.
Proxy cheklovlari
Proxylar mavjud objektlarning eng past darajadagi xatti-harakatlarini oâzgartirish yoki sozlash uchun noyob usul taqdim etadi. Shunga qaramay, u mukammal emas. Cheklovlar mavjud.
Oârnatilgan objektlar: Ichki slotlar
Koâplab oârnatilgan objektlar, masalan Map, Set, Date, Promise va boshqalar âichki slotlarâ deb ataladigan narsalardan foydalanadi.
Bular xususiyatlarga oâxshash, lekin ichki, faqat spetsifikatsiya maqsadlari uchun ajratilgan. Masalan, Map elementlarni ichki slot [[MapData]] da saqlaydi. Oârnatilgan metodlar ularga [[Get]]/[[Set]] ichki metodlari orqali emas, toâgâridan-toâgâri kiradi. Shuning uchun Proxy buni tutib qola olmaydi.
Nega gâamxoârlik? Ular baribir ichki!
Xoâsh, mana muammo. Bunday oârnatilgan objekt proxy qilingandan keyin, proxy bu ichki slotlarga ega boâlmaydi, shuning uchun oârnatilgan metodlar ishlamaydi.
Masalan:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // Xato
Ichki tomondan Map barcha maâlumotlarni oâzining [[MapData]] ichki slotida saqlaydi. Proxy bunday slotga ega emas. Oârnatilgan Map.prototype.set metodi ichki xususiyat this.[[MapData]] ga kirishga harakat qiladi, lekin this=proxy boâlganligi sababli, uni proxy da topa olmaydi va shunchaki ishlamaydi.
Yaxshiyamki, buni tuzatish usuli bor:
let map = new Map();
let proxy = new Proxy(map, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
proxy.set('test', 1);
alert(proxy.get('test')); // 1 (ishlaydi!)
Endi u yaxshi ishlaydi, chunki get trap funktsiya xususiyatlarini, masalan map.set ni, maqsadli objekt (map) ning oâziga bogâlaydi.
Oldingi misoldan farqli oâlaroq, proxy.set(...) ichidagi this ning qiymati proxy emas, balki asl map boâladi. Shuning uchun set ning ichki implementatsiyasi this.[[MapData]] ichki slotiga kirishga harakat qilganda, u muvaffaqiyatli boâladi.
Array da ichki slotlar yoâqMuhim istisno: oârnatilgan Array ichki slotlardan foydalanmaydi. Bu tarixiy sabablarga koâra, chunki u juda uzoq vaqt oldin paydo boâlgan.
Shuning uchun massivni proxy qilishda bunday muammo yoâq.
Shaxsiy maydonlar
Shaxsiy klass maydonlari bilan ham xuddi shunday narsa sodir boâladi.
Masalan, getName() metodi shaxsiy #name xususiyatiga kiradi va proxy qilingandan keyin buziladi:
class User {
#name = "Guest";
getName() {
return this.#name;
}
}
let user = new User();
user = new Proxy(user, {});
alert(user.getName()); // Xato
Sababi shundaki, shaxsiy maydonlar ichki slotlar yordamida amalga oshiriladi. JavaScript ularga kirishda [[Get]]/[[Set]] dan foydalanmaydi.
getName() chaqiruvida this ning qiymati proxy qilingan user va unda shaxsiy maydonlar bilan slot yoâq.
Yana, metodlarni bogâlash bilan yechim ishlaydi:
class User {
#name = "Guest";
getName() {
return this.#name;
}
}
let user = new User();
user = new Proxy(user, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
alert(user.getName()); // Guest
Biroq yechimning kamchiliklari bor, avval tushuntirilganidek: u asl objektni metodga ochib beradi, potensial ravishda uni keyinchalik uzatishga va boshqa proxy qilingan funksionallikni buzishga imkon beradi.
Proxy != target
Proxy va asl objekt turli objektlar. Bu tabiiy, toâgârimi?
Shuning uchun agar biz asl objektni kalit sifatida ishlatsak va keyin uni proxy qilsak, proxy topilmaydi:
let allUsers = new Set();
class User {
constructor(name) {
this.name = name;
allUsers.add(this);
}
}
let user = new User("John");
alert(allUsers.has(user)); // true
user = new Proxy(user, {});
alert(allUsers.has(user)); // false
Koârib turganingizdek, proxy qilgandan keyin biz user ni allUsers toâplamida topa olmaymiz, chunki proxy boshqa objekt.
=== tutib qola olmaydiProxylar koâplab operatorlarni tutib qolishi mumkin, masalan new (construct bilan), in (has bilan), delete (deleteProperty bilan) va hokazo.
Lekin objektlar uchun qatâiy tenglik testini tutib qolish usuli yoâq. Objekt faqat oâziga qatâiy teng, boshqa hech qanday qiymatga emas.
Shuning uchun objektlarni tenglik uchun solishtiradigan barcha amallar va oârnatilgan klasslar objekt va proxy oârtasida farq qiladi. Shaffof almashtirish bu yerda yoâq.
Bekor qilinadigan proxylar
Bekor qilinadigan proxy â bu oâchirib qoâyilishi mumkin boâlgan proxy.
Aytaylik, bizda resurs bor va istalgan vaqtda unga kirishni yopmoqchimiz.
Biz uni hech qanday traplarsiz bekor qilinadigan proxyga oârashimiz mumkin. Bunday proxy amallarni objektga yoânaltiradi va biz uni istalgan vaqtda oâchirib qoâyishimiz mumkin.
Sintaksis:
let {proxy, revoke} = Proxy.revocable(target, handler)
Chaqiruv proxy va uni oâchirish uchun revoke funktsiyasi bilan objekt qaytaradi.
Mana misol:
let object = {
data: "Qimmatli ma'lumot"
};
let {proxy, revoke} = Proxy.revocable(object, {});
// proxy ni objekt o'rniga biror joyga uzatish...
alert(proxy.data); // Qimmatli ma'lumot
// keyinroq kodimizda
revoke();
// proxy endi ishlamaydi (bekor qilingan)
alert(proxy.data); // Xato
revoke() ga chaqiruv proxy dan maqsadli objektga barcha ichki havolalarni olib tashlaydi, shuning uchun ular endi bogâlanmagan.
Dastlab revoke proxy dan alohida, shuning uchun biz proxy ni aylanib oâtkazishimiz mumkin, revoke ni joriy doirada qoldirib.
Biz shuningdek proxy.revoke = revoke oârnatish orqali revoke metodini proxy ga bogâlashimiz mumkin.
Boshqa variant â proxy ni kalit va tegishli revoke ni qiymat sifatida saqlaydigan WeakMap yaratish, bu proxy uchun revoke ni osongina topish imkonini beradi:
let revokes = new WeakMap();
let object = {
data: "Qimmatli ma'lumot"
};
let {proxy, revoke} = Proxy.revocable(object, {});
revokes.set(proxy, revoke);
// ..kodimizning boshqa joyida..
revoke = revokes.get(proxy);
revoke();
alert(proxy.data); // Xato (bekor qilingan)
Biz bu yerda Map oârniga WeakMap dan foydalanamiz, chunki u axlat yigâishni toâsqinlik qilmaydi. Agar proxy objekti âyetib borilmaydiganâ boâlsa (masalan, hech qanday oâzgaruvchi unga havola qilmaydi), WeakMap uni bizga endi kerak boâlmagan revoke bilan birga xotiradan oâchirishga imkon beradi.
Havolalar
Xulosa
Proxy â bu objekt atrofidagi wrapper boâlib, undagi amallarni objektga yoânaltiradi, ixtiyoriy ravishda ulardan baâzilarini tutib qoladi.
U har qanday turdagi objektni, jumladan klasslar va funktsiyalarni oâray oladi.
Sintaksis:
let proxy = new Proxy(target, {
/* traplar */
});
â¦Keyin biz target oârniga hamma joyda proxy dan foydalanishimiz kerak. Proxy ning oâz xususiyatlari yoki metodlari yoâq. Agar trap taqdim etilgan boâlsa, u amallarni tutib qoladi, aks holda uni target objektiga yoânaltiradi.
Biz quyidagilarni tutib qolishimiz mumkin:
- Xususiyatni oâqish (
get), yozish (set), oâchirish (deleteProperty) (hatto mavjud boâlmagan). - Funktsiyani chaqirish (
applytrap). newoperatori (constructtrap).- Koâplab boshqa amallar (toâliq roâyxat maqola boshida va hujjatlarda).
Bu bizga âvirtualâ xususiyatlar va metodlar, standart qiymatlar, kuzatiladigan objektlar, funktsiya dekoratorlari va boshqa koâp narsalarni yaratish imkonini beradi.
Shuningdek objektni turli jihatlarning funksionalligi bilan bezab, turli proxylarda bir necha marta oârashimiz mumkin.
Reflect API Proxy ni toâldirish uchun moâljallangan. Har qanday Proxy trap uchun bir xil argumentlar bilan Reflect chaqiruvi mavjud. Maqsadli objektlarga chaqiruvlarni yoânaltirish uchun ulardan foydalanishimiz kerak.
Proxylarning baâzi cheklovlari bor:
- Oârnatilgan objektlarda âichki slotlarâ bor, ularga kirish proxy qilinmaydi. Yuqoridagi yechimni qarang.
- Xuddi shu narsa shaxsiy klass maydonlari uchun ham amal qiladi, chunki ular ichki tomondan slotlar yordamida amalga oshiriladi. Shuning uchun proxy qilingan metod chaqiruvlari ularga kirish uchun maqsadli objektga
thissifatida ega boâlishi kerak. - Objekt tenglik testlari
===tutib qolinmaydi. - Ishlash: benchmarklar mexanizmga bogâliq, lekin odatda eng oddiy proxy yordamida xususiyatga kirish bir necha marta uzoqroq vaqt oladi. Amalda bu faqat baâzi âboâgâinâ objektlari uchun muhim.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)