الگوهای پراکسی جاوا اسکریپت برای اصلاح رفتار اشیاء را کاوش کنید. اعتبارسنجی، مجازیسازی، ردیابی و تکنیکهای پیشرفته دیگر را با مثالهای کد بیاموزید.
الگوهای پراکسی در جاوا اسکریپت: تسلط بر اصلاح رفتار اشیاء
شیء پراکسی (Proxy) در جاوا اسکریپت مکانیزم قدرتمندی برای رهگیری و سفارشیسازی عملیات بنیادی روی اشیاء فراهم میکند. این قابلیت درهای جدیدی را به روی طیف گستردهای از الگوهای طراحی و تکنیکهای پیشرفته برای کنترل رفتار اشیاء باز میکند. این راهنمای جامع به بررسی الگوهای مختلف پراکسی میپردازد و کاربردهای آنها را با مثالهای کد عملی نشان میدهد.
پراکسی جاوا اسکریپت چیست؟
یک شیء پراکسی، شیء دیگری (هدف یا target) را در بر میگیرد و عملیات آن را رهگیری میکند. این عملیات که به عنوان تله (traps) شناخته میشوند، شامل جستجوی پراپرتی، تخصیص مقدار، شمارش و فراخوانی تابع هستند. پراکسی به شما اجازه میدهد تا منطق سفارشی را تعریف کنید که قبل، بعد یا به جای این عملیات اجرا شود. مفهوم اصلی پراکسی شامل «فرابرامهنویسی» (metaprogramming) است که به شما امکان میدهد رفتار خود زبان جاوا اسکریپت را دستکاری کنید.
سینتکس اصلی برای ایجاد یک پراکسی به این صورت است:
const proxy = new Proxy(target, handler);
- target: شیء اصلی که میخواهید برای آن پراکسی ایجاد کنید.
- handler: شیئی حاوی متدها (تلهها) که تعریف میکنند پراکسی چگونه عملیات روی هدف را رهگیری کند.
تلههای رایج پراکسی
شیء handler میتواند چندین تله را تعریف کند. در اینجا برخی از رایجترین آنها آورده شده است:
- get(target, property, receiver): دسترسی به پراپرتی را رهگیری میکند (مثلاً
obj.property
). - set(target, property, value, receiver): تخصیص مقدار به پراپرتی را رهگیری میکند (مثلاً
obj.property = value
). - has(target, property): عملگر
in
را رهگیری میکند (مثلاً'property' in obj
). - deleteProperty(target, property): عملگر
delete
را رهگیری میکند (مثلاًdelete obj.property
). - apply(target, thisArg, argumentsList): فراخوانی توابع را رهگیری میکند (زمانی که هدف یک تابع باشد).
- construct(target, argumentsList, newTarget): عملگر
new
را رهگیری میکند (زمانی که هدف یک تابع سازنده باشد). - getPrototypeOf(target): فراخوانیهای
Object.getPrototypeOf()
را رهگیری میکند. - setPrototypeOf(target, prototype): فراخوانیهای
Object.setPrototypeOf()
را رهگیری میکند. - isExtensible(target): فراخوانیهای
Object.isExtensible()
را رهگیری میکند. - preventExtensions(target): فراخوانیهای
Object.preventExtensions()
را رهگیری میکند. - getOwnPropertyDescriptor(target, property): فراخوانیهای
Object.getOwnPropertyDescriptor()
را رهگیری میکند. - defineProperty(target, property, descriptor): فراخوانیهای
Object.defineProperty()
را رهگیری میکند. - ownKeys(target): فراخوانیهای
Object.getOwnPropertyNames()
وObject.getOwnPropertySymbols()
را رهگیری میکند.
الگوهای پراکسی و موارد استفاده
بیایید برخی از الگوهای رایج پراکسی و نحوه استفاده از آنها در سناریوهای واقعی را بررسی کنیم:
۱. اعتبارسنجی
الگوی اعتبارسنجی از یک پراکسی برای اعمال محدودیتها بر تخصیص مقادیر به پراپرتیها استفاده میکند. این کار برای اطمینان از یکپارچگی دادهها مفید است.
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('سن یک عدد صحیح نیست');
}
if (value < 0) {
throw new RangeError('سن باید یک عدد صحیح غیر منفی باشد');
}
}
// رفتار پیشفرض برای ذخیره مقدار
obj[prop] = value;
// نشاندهنده موفقیت
return true;
}
};
let person = {};
let proxy = new Proxy(person, validator);
proxy.age = 25; // معتبر
console.log(proxy.age); // خروجی: 25
try {
proxy.age = 'young'; // TypeError ایجاد میکند
} catch (e) {
console.log(e); // خروجی: TypeError: سن یک عدد صحیح نیست
}
try {
proxy.age = -10; // RangeError ایجاد میکند
} catch (e) {
console.log(e); // خروجی: RangeError: سن باید یک عدد صحیح غیر منفی باشد
}
مثال: یک پلتفرم تجارت الکترونیک را در نظر بگیرید که دادههای کاربر نیاز به اعتبارسنجی دارند. یک پراکسی میتواند قوانینی را برای سن، فرمت ایمیل، قدرت رمز عبور و سایر فیلدها اعمال کند و از ذخیره دادههای نامعتبر جلوگیری کند.
۲. مجازیسازی (بارگذاری تنبل)
مجازیسازی، که به آن بارگذاری تنبل (lazy loading) نیز گفته میشود، بارگذاری منابع پرهزینه را تا زمانی که واقعاً به آنها نیاز باشد به تأخیر میاندازد. یک پراکسی میتواند به عنوان یک جایگزین برای شیء واقعی عمل کند و آن را تنها زمانی که به یک پراپرتی دسترسی پیدا میشود، بارگذاری کند.
const expensiveData = {
load: function() {
console.log('در حال بارگذاری دادههای پرهزینه...');
// شبیهسازی یک عملیات زمانبر (مثلاً واکشی از پایگاه داده)
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: 'این دادههای پرهزینه است'
});
}, 2000);
});
}
};
const lazyLoadHandler = {
get: function(target, prop) {
if (prop === 'data') {
console.log('دسترسی به دادهها، در صورت لزوم بارگذاری میشود...');
return target.load().then(result => {
target.data = result.data; // ذخیره دادههای بارگذاریشده
return result.data;
});
} else {
return target[prop];
}
}
};
const lazyData = new Proxy(expensiveData, lazyLoadHandler);
console.log('دسترسی اولیه...');
lazyData.data.then(data => {
console.log('داده:', data); // خروجی: داده: این دادههای پرهزینه است
});
console.log('دسترسی بعدی...');
lazyData.data.then(data => {
console.log('داده:', data); // خروجی: داده: این دادههای پرهزینه است (بارگذاری شده از حافظه پنهان)
});
مثال: یک پلتفرم رسانه اجتماعی بزرگ را تصور کنید که پروفایلهای کاربری آن حاوی جزئیات متعدد و رسانههای مرتبط است. بارگذاری فوری تمام دادههای پروفایل میتواند ناکارآمد باشد. مجازیسازی با یک پراکسی اجازه میدهد ابتدا اطلاعات اولیه پروفایل بارگذاری شود و سپس جزئیات بیشتر یا محتوای رسانهای تنها زمانی که کاربر به آن بخشها میرود، بارگذاری شود.
۳. ثبت وقایع و ردیابی
پراکسیها میتوانند برای ردیابی دسترسی به پراپرتیها و تغییرات آنها استفاده شوند. این کار برای اشکالزدایی، حسابرسی و نظارت بر عملکرد ارزشمند است.
const logHandler = {
get: function(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value) {
console.log(`SET ${prop} to ${value}`);
target[prop] = value;
return true;
}
};
let obj = { name: 'Alice' };
let proxy = new Proxy(obj, logHandler);
console.log(proxy.name); // خروجی: GET name, Alice
proxy.age = 30; // خروجی: SET age to 30
مثال: در یک برنامه ویرایش اسناد مشترک، یک پراکسی میتواند هر تغییری که در محتوای سند ایجاد میشود را ردیابی کند. این کار امکان ایجاد یک ردپای حسابرسی، فعال کردن قابلیت undo/redo و ارائه بینش در مورد مشارکت کاربران را فراهم میکند.
۴. نماهای فقط-خواندنی
پراکسیها میتوانند نماهای فقط-خواندنی از اشیاء ایجاد کنند و از تغییرات تصادفی جلوگیری کنند. این برای محافظت از دادههای حساس مفید است.
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`نمیتوان پراپرتی ${prop} را تنظیم کرد: شیء فقط-خواندنی است`);
return false; // نشان میدهد که عملیات set ناموفق بود
},
deleteProperty: function(target, prop) {
console.error(`نمیتوان پراپرتی ${prop} را حذف کرد: شیء فقط-خواندنی است`);
return false; // نشان میدهد که عملیات delete ناموفق بود
}
};
let data = { name: 'Bob', age: 40 };
let readOnlyData = new Proxy(data, readOnlyHandler);
try {
readOnlyData.age = 41; // خطا ایجاد میکند
} catch (e) {
console.log(e); // خطایی ایجاد نمیشود چون تله 'set' مقدار false برمیگرداند.
}
try {
delete readOnlyData.name; // خطا ایجاد میکند
} catch (e) {
console.log(e); // خطایی ایجاد نمیشود چون تله 'deleteProperty' مقدار false برمیگرداند.
}
console.log(data.age); // خروجی: 40 (بدون تغییر)
مثال: یک سیستم مالی را در نظر بگیرید که در آن برخی از کاربران دسترسی فقط-خواندنی به اطلاعات حساب دارند. میتوان از یک پراکسی برای جلوگیری از تغییر موجودی حساب یا سایر دادههای حیاتی توسط این کاربران استفاده کرد.
۵. مقادیر پیشفرض
یک پراکسی میتواند مقادیر پیشفرض برای پراپرتیهای ناموجود ارائه دهد. این کار کد را سادهتر کرده و از بررسیهای null/undefined جلوگیری میکند.
const defaultValuesHandler = {
get: function(target, prop, receiver) {
if (!(prop in target)) {
console.log(`پراپرتی ${prop} یافت نشد، مقدار پیشفرض برگردانده میشود.`);
return 'مقدار پیشفرض'; // یا هر مقدار پیشفرض مناسب دیگر
}
return Reflect.get(target, prop, receiver);
}
};
let config = { apiUrl: 'https://api.example.com' };
let configWithDefaults = new Proxy(config, defaultValuesHandler);
console.log(configWithDefaults.apiUrl); // خروجی: https://api.example.com
console.log(configWithDefaults.timeout); // خروجی: پراپرتی timeout یافت نشد، مقدار پیشفرض برگردانده میشود. مقدار پیشفرض
مثال: در یک سیستم مدیریت پیکربندی، یک پراکسی میتواند مقادیر پیشفرض برای تنظیمات ناموجود ارائه دهد. به عنوان مثال، اگر یک فایل پیکربندی زمان وقفه اتصال به پایگاه داده را مشخص نکند، پراکسی میتواند یک مقدار پیشفرض از پیش تعریف شده را برگرداند.
۶. فراداده و حاشیهنویسی
پراکسیها میتوانند فراداده یا حاشیهنویسیها را به اشیاء متصل کنند و اطلاعات اضافی را بدون تغییر شیء اصلی فراهم کنند.
const metadataHandler = {
get: function(target, prop, receiver) {
if (prop === '__metadata__') {
return { description: 'این فراداده برای شیء است' };
}
return Reflect.get(target, prop, receiver);
}
};
let article = { title: 'مقدمهای بر پراکسیها', content: '...' };
let articleWithMetadata = new Proxy(article, metadataHandler);
console.log(articleWithMetadata.title); // خروجی: مقدمهای بر پراکسیها
console.log(articleWithMetadata.__metadata__.description); // خروجی: این فراداده برای شیء است
مثال: در یک سیستم مدیریت محتوا، یک پراکسی میتواند فرادادهای مانند اطلاعات نویسنده، تاریخ انتشار و کلمات کلیدی را به مقالات متصل کند. این فراداده میتواند برای جستجو، فیلتر کردن و دستهبندی محتوا استفاده شود.
۷. رهگیری توابع
پراکسیها میتوانند فراخوانی توابع را رهگیری کنند و به شما امکان میدهند تا منطق ثبت وقایع، اعتبارسنجی یا سایر پردازشهای پیش یا پس از اجرا را اضافه کنید.
const functionInterceptor = {
apply: function(target, thisArg, argumentsList) {
console.log('فراخوانی تابع با آرگومانها:', argumentsList);
const result = target.apply(thisArg, argumentsList);
console.log('تابع برگرداند:', result);
return result;
}
};
function add(a, b) {
return a + b;
}
let proxiedAdd = new Proxy(add, functionInterceptor);
let sum = proxiedAdd(5, 3); // خروجی: فراخوانی تابع با آرگومانها: [5, 3], تابع برگرداند: 8
console.log(sum); // خروجی: 8
مثال: در یک برنامه بانکی، یک پراکسی میتواند فراخوانی توابع تراکنش را رهگیری کند، هر تراکنش را ثبت کرده و بررسیهای تشخیص تقلب را قبل از اجرای تراکنش انجام دهد.
۸. رهگیری سازنده
پراکسیها میتوانند فراخوانی سازندهها را رهگیری کنند و به شما امکان میدهند تا ایجاد شیء را سفارشی کنید.
const constructorInterceptor = {
construct: function(target, argumentsList, newTarget) {
console.log('ایجاد یک نمونه جدید از', target.name, 'با آرگومانها:', argumentsList);
const obj = new target(...argumentsList);
console.log('نمونه جدید ایجاد شد:', obj);
return obj;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let ProxiedPerson = new Proxy(Person, constructorInterceptor);
let person = new ProxiedPerson('Alice', 28); // خروجی: ایجاد یک نمونه جدید از Person با آرگومانها: ['Alice', 28], نمونه جدید ایجاد شد: Person { name: 'Alice', age: 28 }
console.log(person);
مثال: در یک چارچوب توسعه بازی، یک پراکسی میتواند ایجاد اشیاء بازی را رهگیری کند، به طور خودکار شناسههای منحصر به فرد اختصاص دهد، کامپوننتهای پیشفرض را اضافه کند و آنها را در موتور بازی ثبت کند.
ملاحظات پیشرفته
- عملکرد: در حالی که پراکسیها انعطافپذیری ارائه میدهند، میتوانند سربار عملکردی ایجاد کنند. مهم است که کد خود را بنچمارک و پروفایل کنید تا اطمینان حاصل شود که مزایای استفاده از پراکسیها بر هزینههای عملکردی، به ویژه در برنامههای حساس به عملکرد، غلبه دارد.
- سازگاری: پراکسیها یک افزودنی نسبتاً جدید به جاوا اسکریپت هستند، بنابراین مرورگرهای قدیمی ممکن است از آنها پشتیبانی نکنند. از تشخیص ویژگی یا polyfillها برای اطمینان از سازگاری با محیطهای قدیمیتر استفاده کنید.
- پراکسیهای قابل ابطال: متد
Proxy.revocable()
یک پراکسی ایجاد میکند که میتوان آن را باطل کرد. باطل کردن یک پراکسی از رهگیری هرگونه عملیات بیشتر جلوگیری میکند. این میتواند برای اهداف امنیتی یا مدیریت منابع مفید باشد. - Reflect API: Reflect API متدهایی برای انجام رفتار پیشفرض تلههای پراکسی فراهم میکند. استفاده از
Reflect
تضمین میکند که کد پراکسی شما با مشخصات زبان سازگار است.
نتیجهگیری
پراکسیهای جاوا اسکریپت یک مکانیزم قدرتمند و همهکاره برای سفارشیسازی رفتار اشیاء فراهم میکنند. با تسلط بر الگوهای مختلف پراکسی، میتوانید کدی قویتر، قابل نگهداریتر و کارآمدتر بنویسید. چه در حال پیادهسازی اعتبارسنجی، مجازیسازی، ردیابی یا سایر تکنیکهای پیشرفته باشید، پراکسیها راهحلی انعطافپذیر برای کنترل نحوه دسترسی و دستکاری اشیاء ارائه میدهند. همیشه پیامدهای عملکردی را در نظر بگیرید و از سازگاری با محیطهای هدف خود اطمینان حاصل کنید. پراکسیها ابزاری کلیدی در زرادخانه توسعهدهنده مدرن جاوا اسکریپت هستند که تکنیکهای قدرتمند فرابرامهنویسی را امکانپذیر میسازند.
برای مطالعه بیشتر
- شبکه توسعهدهندگان موزیلا (MDN): پراکسی جاوا اسکریپت
- کاوش پراکسیهای جاوا اسکریپت: مقاله Smashing Magazine