استكشف أنماط وكيل JavaScript لتعديل سلوك الكائنات. تعرف على التحقق من الصحة، والمحاكاة الافتراضية، والتتبع، وتقنيات متقدمة أخرى مع أمثلة برمجية.
أنماط وكيل JavaScript: إتقان تعديل سلوك الكائنات
يوفر كائن وكيل (Proxy) JavaScript آلية قوية لاعتراض وتخصيص العمليات الأساسية على الكائنات. تفتح هذه القدرة الأبواب أمام مجموعة واسعة من أنماط التصميم والتقنيات المتقدمة للتحكم في سلوك الكائنات. يستكشف هذا الدليل الشامل أنماط الوكيل المختلفة، ويوضح استخداماتها بأمثلة برمجية عملية.
ما هو وكيل JavaScript؟
يقوم كائن الوكيل بتغليف كائن آخر (الهدف) ويعترض عملياته. تُعرف هذه العمليات، باسم "الفخاخ" (traps)، وتشمل البحث عن الخصائص، والتعيين، والتعداد، واستدعاء الدوال. يتيح لك الوكيل تحديد منطق مخصص ليتم تنفيذه قبل هذه العمليات أو بعدها أو بدلاً منها. المفهوم الأساسي للوكيل يتضمن "البرمجة الوصفية" (metaprogramming) التي تمكنك من التلاعب بسلوك لغة JavaScript نفسها.
الصيغة الأساسية لإنشاء وكيل هي:
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()
.
أنماط الوكيل وحالات الاستخدام
دعنا نستكشف بعض أنماط الوكيل الشائعة وكيف يمكن تطبيقها في سيناريوهات العالم الحقيقي:
1. التحقق من الصحة
يستخدم نمط التحقق من الصحة وكيلاً لفرض قيود على تعيينات الخصائص. هذا مفيد لضمان سلامة البيانات.
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: يجب أن يكون العمر عددًا صحيحًا غير سالب
}
مثال: فكر في منصة تجارة إلكترونية حيث تحتاج بيانات المستخدم إلى التحقق من صحتها. يمكن للوكيل فرض قواعد على العمر، وتنسيق البريد الإلكتروني، وقوة كلمة المرور، وغيرها من الحقول، مما يمنع تخزين البيانات غير الصالحة.
2. المحاكاة الافتراضية (التحميل الكسول)
المحاكاة الافتراضية، المعروفة أيضًا بالتحميل الكسول، تؤخر تحميل الموارد المكلفة حتى تصبح هناك حاجة فعلية إليها. يمكن للوكيل أن يعمل كعنصر نائب للكائن الحقيقي، حيث يقوم بتحميله فقط عند الوصول إلى إحدى خصائصه.
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); // المخرج: البيانات: هذه هي البيانات المكلفة (تم تحميله من الذاكرة المؤقتة)
});
مثال: تخيل منصة وسائط اجتماعية كبيرة بها ملفات تعريف مستخدمين تحتوي على تفاصيل عديدة ووسائط مرتبطة بها. قد يكون تحميل جميع بيانات الملف الشخصي على الفور غير فعال. تتيح المحاكاة الافتراضية باستخدام الوكيل تحميل معلومات الملف الشخصي الأساسية أولاً، ثم تحميل التفاصيل الإضافية أو محتوى الوسائط فقط عندما ينتقل المستخدم إلى تلك الأقسام.
3. التسجيل والتتبع
يمكن استخدام الوكلاء لتتبع الوصول إلى الخصائص وتعديلاتها. وهذا قيّم لأغراض تصحيح الأخطاء والمراجعة ومراقبة الأداء.
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
مثال: في تطبيق تحرير المستندات التعاوني، يمكن للوكيل تتبع كل تغيير يتم إجراؤه على محتوى المستند. وهذا يسمح بإنشاء سجل تدقيق، وتمكين وظائف التراجع/الإعادة، وتقديم رؤى حول مساهمات المستخدمين.
4. العروض للقراءة فقط
يمكن للوكلاء إنشاء عروض للكائنات للقراءة فقط، مما يمنع التعديلات العرضية. وهذا مفيد لحماية البيانات الحساسة.
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`لا يمكن تعيين الخاصية ${prop}: الكائن للقراءة فقط`);
return false; // للإشارة إلى فشل عملية التعيين
},
deleteProperty: function(target, prop) {
console.error(`لا يمكن حذف الخاصية ${prop}: الكائن للقراءة فقط`);
return false; // للإشارة إلى فشل عملية الحذف
}
};
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 (لم يتغير)
مثال: فكر في نظام مالي حيث يمتلك بعض المستخدمين وصولاً للقراءة فقط لمعلومات الحساب. يمكن استخدام الوكيل لمنع هؤلاء المستخدمين من تعديل أرصدة الحسابات أو البيانات الهامة الأخرى.
5. القيم الافتراضية
يمكن للوكيل توفير قيم افتراضية للخصائص المفقودة. هذا يبسط الكود ويتجنب عمليات التحقق من القيم الفارغة (null/undefined).
const defaultValuesHandler = {
get: function(target, prop, receiver) {
if (!(prop in target)) {
console.log(`الخاصية ${prop} غير موجودة، يتم إرجاع القيمة الافتراضية.`);
return 'Default Value'; // أو أي قيمة افتراضية أخرى مناسبة
}
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 غير موجودة، يتم إرجاع القيمة الافتراضية. Default Value
مثال: في نظام إدارة التكوين، يمكن للوكيل توفير قيم افتراضية للإعدادات المفقودة. على سبيل المثال، إذا لم يحدد ملف التكوين مهلة اتصال قاعدة البيانات، يمكن للوكيل إرجاع قيمة افتراضية محددة مسبقًا.
6. البيانات الوصفية والتعليقات التوضيحية
يمكن للوكلاء إرفاق بيانات وصفية أو تعليقات توضيحية بالكائنات، مما يوفر معلومات إضافية دون تعديل الكائن الأصلي.
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); // المخرج: هذه بيانات وصفية للكائن
مثال: في نظام إدارة المحتوى، يمكن للوكيل إرفاق بيانات وصفية بالمقالات، مثل معلومات المؤلف وتاريخ النشر والكلمات الرئيسية. يمكن استخدام هذه البيانات الوصفية للبحث والتصفية وتصنيف المحتوى.
7. اعتراض الدوال
يمكن للوكلاء اعتراض استدعاءات الدوال، مما يسمح لك بإضافة تسجيل أو تحقق من الصحة أو منطق معالجة آخر قبل أو بعد التنفيذ.
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
مثال: في تطبيق مصرفي، يمكن للوكيل اعتراض استدعاءات دوال المعاملات، وتسجيل كل معاملة وإجراء فحوصات للكشف عن الاحتيال قبل تنفيذ المعاملة.
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);
مثال: في إطار عمل تطوير الألعاب، يمكن للوكيل اعتراض إنشاء كائنات اللعبة، وتعيين معرفات فريدة تلقائيًا، وإضافة مكونات افتراضية، وتسجيلها في محرك اللعبة.
اعتبارات متقدمة
- الأداء: على الرغم من أن الوكلاء يوفرون المرونة، إلا أنهم يمكن أن يضيفوا عبئًا على الأداء. من المهم قياس أداء الكود الخاص بك وتحليله للتأكد من أن فوائد استخدام الوكلاء تفوق تكاليف الأداء، خاصة في التطبيقات التي يكون فيها الأداء حرجًا.
- التوافق: يعد الوكلاء إضافة حديثة نسبيًا إلى JavaScript، لذا قد لا تدعمها المتصفحات القديمة. استخدم اكتشاف الميزات أو polyfills لضمان التوافق مع البيئات القديمة.
- الوكلاء القابلون للإلغاء: تنشئ دالة
Proxy.revocable()
وكيلاً يمكن إلغاؤه. يؤدي إلغاء الوكيل إلى منع اعتراض أي عمليات أخرى. يمكن أن يكون هذا مفيدًا لأغراض الأمان أو إدارة الموارد. - واجهة برمجة تطبيقات Reflect: توفر واجهة برمجة تطبيقات Reflect دوال لتنفيذ السلوك الافتراضي لفخاخ الوكيل. يضمن استخدام
Reflect
أن يتصرف كود الوكيل الخاص بك بشكل متسق مع مواصفات اللغة.
الخاتمة
توفر وكلاء JavaScript آلية قوية ومتعددة الاستخدامات لتخصيص سلوك الكائنات. من خلال إتقان أنماط الوكيل المختلفة، يمكنك كتابة كود أكثر قوة وقابلية للصيانة وكفاءة. سواء كنت تنفذ التحقق من الصحة، أو المحاكاة الافتراضية، أو التتبع، أو تقنيات متقدمة أخرى، فإن الوكلاء يقدمون حلاً مرنًا للتحكم في كيفية الوصول إلى الكائنات والتلاعب بها. ضع في اعتبارك دائمًا الآثار المترتبة على الأداء وتأكد من التوافق مع البيئات المستهدفة. يعد الوكلاء أداة رئيسية في ترسانة مطور JavaScript الحديث، مما يتيح تقنيات برمجة وصفية قوية.
استكشاف إضافي
- شبكة مطوري موزيلا (MDN): وكيل JavaScript
- استكشاف وكلاء JavaScript: مقالة من مجلة Smashing