پروکسیها، پایهی
مباحث AOP هستند. این اشیاء ویژهی ES 6، امکان ردیابی تغییرات را بر روی اشیاء جاوا اسکریپتی فراهم میکنند. ابتداییترین مثالی را که در این زمینه میتوان ارائه داد، بررسی تغییرات خواص Get و Set اشیاء هستند. فرض کنید شیء unicorn به صورت زیر تعریف شدهاست:
var unicorn = {
legs: 4,
color: 'brown',
horn: true
};
اکنون میخواهیم اگر کسی درخواست مقدار خاصیت color این شیء را ارائه داد، بجای رنگ قهوهای، یک مقدار سفارشی سازی شده را دریافت کند. برای تداخل در این بین و کنترل مقدار بازگشت داده شدهی توسط یک شیء مفروض، میتوان از شیء جدیدی به نام Proxy استفاده کرد:
var proxyUnicorn = new Proxy(unicorn, {
get: function(target, property) {
if(property === 'color') {
return 'awesome ' + target[property];
} else {
return target[property];
}
}
});
کار شیء پروکسی، ایجاد یک شیء جدید از unicorn نیست. بلکه به صورت غشایی نامرئی ظاهر شده و محصور کنندهی این شیء میشود. بنابراین کلیهی درخواستهای رسیدهی به unicorn، ابتدا باید از این غشاء رد شود و سپس به unicorn برسد. اینجا است که امکان ردیابی و همچنین سفارشی سازی دسترسی به خواص را میتوان پیاده سازی کرد.
شیء Proxy در ES 6 دو پارامتر را دریافت میکند. پارامتر اول آن، شیء اصلی است که باید محصور شود و پارامتر دوم آن مشخص میکند که چه عملیاتی باید تحت کنترل و سفارشی سازی قرار گیرد. در این مثال عملیات get تحت نظر قرار گرفتهاست و برای اینکار متدی که تعریف شده (به آن handler نیز میگویند)، پارامتر اول آن target یا همان unicorn در این مثال است و property نام خاصیتی است که هم اکنون قرار است مقدار آن بازگشت داده شود. در مثال فوق دو حالت دسترسی به خاصیتی به نام color و همچنین سایر خواصی که این نام را ندارند، پیاده سازی شدهاست.
پس از این عملیات، اگر به خواص ارائه شدهی توسط شیء پروکسی دسترسی پیدا کنیم، یک چنین خروجی را دریافت خواهیم کرد:
console.log(proxyUnicorn.legs); //4
console.log(proxyUnicorn.color); //'awesome brown'
مثالی دیگر در این زمینه میتواند کنترل عملیات دسترسی به حالت set باشد (هر دوی این حالتها را با یک شیء پروکسی نیز میتوان مدیریت کرد):
var proxyUnicorn = new Proxy(unicorn, {
set: function(target, property, value) {
if(property === 'horn' && value === false) {
console.log('unicorn cannot ever lose its horn!');
} else {
target[property] = value;
}
}
});
در حالت set، متد handler تعریف شده، پارامتر سومی را به نام value دارد و این مقدار، مساوی مقداری است که هم اکنون توسط کاربر تنظیم شدهاست. بنابراین در اینجا میتوان پیاده سازی منطق خاصی را پیگیری کرد. برای مثال در اینجا اگر خاصیت مدنظر horn باشد و کاربر سعی کند مقدار false را به آن نسبت دهد، توسط این پروکسی از انجام عملیات منع خواهد شد.
ردگیری فراخوانیهای توابع توسط پروکسیهای ES 6
در ادامه همان شیء unicorn را مشاهده میکنید که متد hornAttack نیز به آن اضافه شدهاست.
var unicorn = {
legs: 4,
color: 'brown',
horn: true,
hornAttack: function(target) {
return target.name + ' was obliterated!';
}
};
اکنون میخواهیم دسترسی به متد hornAttack را تحت نظر قرار داده و اجازهی استفادهی از آنرا به سایر اشیاء ندهیم.
var thief = { name: 'Rupert'}
thief.attack = unicorn.hornAttack;
thief.attack();
برای نمونه در این حالت نمیخواهیم که شیء thief بتواند از hornAttack یک unicorn استفاده کند. به همین جهت برای unicorn.hornAttack یک پروکسی جدید را تعریف میکنیم که دسترسی به آنرا تحت نظر قرار دهد:
unicorn.hornAttack = new Proxy(unicorn.hornAttack, {
apply: function(target, context, args) {
if(context !== unicorn) {
return 'nobody can use unicorn horn but unicorn!';
} else {
return target.apply(context, args);
}
}
});
پس از این تعریف و انتساب، unicorn.hornAttack دیگر همان unicorn.hornAttack اصلی نخواهد بود و اکنون یک proxy object است. در این پروکسی برای کنترل متدها از کلید apply استفاده میشود. متد handler آن دارای سه پارامتر است. پارامتر target همان متد مدنظر است. پارامتر context شیءایی است که قرار است این متد را فراخوانی کند. پارامتر سوم نیز لیست پارامترهای ارسالی به متد است.
در ادامه اگر متد thief.attack فراخوانی شود، این فراخوانی عمل نکرده (چون حالت context !== unicorn برقرار است) و پیام «nobody can use unicorn horn but unicorn» نمایش داده میشود.
در این متد handler (پارامتر دوم شیء پروکسی) مواردی مانند افزودن یا حذف خواص را نیز میتوان تحت کنترل قرار داد:
function NOPE() {
throw new Error("can't modify read-only view");
}
var handler = {
// Override all five mutating methods.
set: NOPE,
defineProperty: NOPE,
deleteProperty: NOPE,
preventExtensions: NOPE,
setPrototypeOf: NOPE
};