البرمجة الوصفية في JavaScript: عندما يكتب الكود نفسه بنفسه
\n\nتخيل أنك تكتب برنامجًا قادرًا على تحليل وتعديل سلوكه الخاص أثناء التنفيذ. هل يبدو هذا كخيال علمي؟ مرحبًا بك في عالم البرمجة الوصفية في JavaScript!
\n\nتتيح هذه التقنية للمطور تجاوز حدود الكود التقليدي والتحكم في بنية الكائنات نفسها، واعتراض استدعاءات الدوال، وحتى إنشاء خصائص افتراضية غير موجودة في الذاكرة. في هذه المقالة، سنشرح الركائز الثلاث للبرمجة الوصفية: Proxy وReflect وSymbol. ستتعلم كيفية استخدامها لبناء واجهات برمجية ذكية، وتنفيذ الحسابات البطيئة (lazy evaluation)، وحماية البيانات من التعديلات غير المقصودة.
\n\nإذا كنت قد أتقنت أساسيات JavaScript وتريد الانتقال إلى المستوى التالي، فهذا المحتوى مناسب لك. لن نضيع الوقت في الأساسيات؛ بل سننتقل مباشرة إلى التقنيات المتقدمة مع أمثلة عملية.
\n\nما هي البرمجة الوصفية ولماذا نحتاجها؟
\n\nفي البرمجة التقليدية، نكتب كودًا يتعامل مع البيانات. أما في البرمجة الوصفية، فنكتب كودًا يتعامل مع كود آخر أو بنية الكائنات. وهذا يمنحنا إمكانيات مذهلة:
\n\n- \n
- التحقق من الصحة والأمان — التحقق تلقائيًا من القيم التي يتم تعيينها للخصائص. \n
- تسجيل الأحداث وتصحيح الأخطاء — اعتراض أي عملية على الكائن وعرضها في وحدة التحكم. \n
- الخصائص الافتراضية — إنشاء خصائص تُحسب فوريًا دون شغل مساحة في الذاكرة. \n
- إنشاء لغات خاصة بالمجال (DSL) — بناء سلاسل استدعاءات مثل jQuery أو Vue. \n
توفر JavaScript ثلاث أدوات قوية لتحقيق ذلك. دعنا نشرح كلًا منها.
\n\nProxy: جاسوسك الشخصي في عالم الكائنات
\n\nProxy هو كائن غلاف (wrapper) يتيح اعتراض وتعديل العمليات الأساسية على كائن آخر (الكائن الهدف). يمكنك "التنصت" على قراءة الخصائص، الكتابة، الحذف، استدعاء الدوال، وحتى التحقق من وجود خاصية باستخدام العامل in.
الصيغة بسيطة: new Proxy(target, handler)، حيث handler هو كائن يحتوي على دوال المصيدة (traps).
مثال 1: التحقق من صحة البيانات باستخدام Proxy
\n\nلنفترض أن لدينا كائن مستخدم، ونريد أن يكون العمر رقمًا صحيحًا بين 0 و120، والاسم سلسلة نصية غير فارغة. بدون Proxy، كنا سنحتاج إلى كتابة دوال getter وsetter لكل حقل. مع Proxy، يتم ذلك بأناقة:
\n\nconst user = {\n name: 'أحمد',\n age: 25\n};\n\nconst validator = {\n set(target, property, value) {\n if (property === 'age') {\n if (!Number.isInteger(value)) {\n throw new TypeError('العمر يجب أن يكون رقمًا صحيحًا');\n }\n if (value < 0 || value > 120) {\n throw new RangeError('العمر خارج النطاق المسموح');\n }\n }\n if (property === 'name') {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new TypeError('الاسم يجب أن يكون سلسلة نصية غير فارغة');\n }\n }\n // إذا كان كل شيء صحيحًا، احفظ القيمة\n target[property] = value;\n return true; // أبلغ أن العملية نجحت\n }\n};\n\nconst proxiedUser = new Proxy(user, validator);\n\n// اختبار\nproxiedUser.age = 30; // صحيح\nconsole.log(proxiedUser.age); // 30\n\nproxiedUser.age = -5; // خطأ: RangeError\nproxiedUser.name = ''; // خطأ: TypeError\n\nلاحظ أننا اعترضنا عملية التعيين وأضفنا منطقنا الخاص. هذا هو البرمجة الوصفية في JavaScript بشكلها النقي — كود يتحكم في سلوك كود آخر.
\n\nمثال 2: الحسابات البطيئة والخصائص الافتراضية
\n\nتخيل أن لدينا