مستوى القارئ: متوسط — تحتاج فهم سابق لـ objects و functions في JavaScript، وفكرة عامة عن getter/setter.
الـ Proxy في JavaScript مش feature تستعرض بيها في مقابلة. هو الحل الوحيد لما تحتاج تراقب أو تتحكم في أي عملية على object بدون ما تعدّل الكود اللي بيستخدمه. لو جربت قبل كده تستخدم getter/setter وحسّيت إن لازم تعرف اسم كل property مقدمًا، الـ Proxy هو اللي بيكسر القيد ده.
JavaScript Proxy: الاعتراض قبل الوصول للـ Object
المشكلة باختصار
تخيل إن عندك object بيتغيّر من 12 مكان مختلف في الكود. يوم واحد بتلاقي قيمة غريبة فيه، ومش عارف مين كتبها. الحل التقليدي: تروح كل مكان، تضيف console.log، تجرّب، وترجع تشيلهم. ده شغل أيام.
الـ Proxy بيخليك تعمل نفس المراقبة في 5 سطور، من غير ما تلمس أي ملف من الـ 12 ده. أكتر من كده، يقدر يرفض أي قيمة غلط قبل ما توصل للـ object أصلاً.
اشرحلي بمثال بسيط الأول
تخيل سكرتير في مكتب مدير. أي حد عايز يكلم المدير، لازم يعدّي على السكرتير. السكرتير يقدر:
- يقول "المدير مش موجود" بدل ما يدخّل الزائر.
- يسجّل اسم كل زائر ووقت الزيارة.
- يرفض الزائر لو ملوش موعد.
- يعدّل رسالة الزائر قبل ما توصل.
المدير نفسه مش لازم يعرف إن في سكرتير، ومش لازم يغيّر شغله. الـ Proxy في JavaScript بيلعب دور السكرتير ده مع object.
التعريف العلمي بدقة
الـ Proxy هو object بيلف object تاني (اسمه target)، ومعاه object تاني فيه دوال اسمها traps (اسمه handler). كل trap بيعترض عملية أساسية محددة من الـ internal methods اللي JavaScript بيستخدمها على objects.
المواصفة الرسمية في ECMAScript بتعرّف 13 trap. أكترهم استخدامًا: get, set, has, deleteProperty, apply, construct, ownKeys. أي عملية ما تعرّفش لها trap، بترجع بشكل افتراضي للـ target من غير تدخّل.
أبسط Proxy ممكن تكتبه
const user = { name: "أحمد", age: 30 };
const tracked = new Proxy(user, {
get(target, prop) {
console.log(`قراءة: ${String(prop)}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`كتابة: ${String(prop)} = ${value}`);
return Reflect.set(target, prop, value);
}
});
tracked.name; // قراءة: name
tracked.age = 31; // كتابة: age = 31
console.log(user.age); // 31 — التغيير وصل للـ target الأصلي
لاحظ Reflect.get و Reflect.set. مش لازم تستخدمهم نظريًا — تقدر تكتب target[prop] مباشرة. عمليًا، Reflect بيتعامل صح مع inheritance و receivers، وبيوفّر عليك مفاجآت مع classes فيها this غريب.
حالة استخدام حقيقية: Validation قبل ما الداتا تتحفظ
السيناريو: عندك form لإنشاء حساب مستخدم، والداتا بتتجمّع في object قبل الإرسال. عايز ترفض أي قيمة غلط في اللحظة اللي اتكتبت فيها، مش بعد الـ submit.
function createValidatedUser() {
const schema = {
age: (v) => typeof v === "number" && v > 0 && v < 150,
email: (v) => typeof v === "string" && /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v),
name: (v) => typeof v === "string" && v.trim().length >= 2,
};
return new Proxy({}, {
set(target, prop, value) {
const validator = schema[prop];
if (validator && !validator(value)) {
throw new TypeError(`قيمة غير صالحة لـ ${String(prop)}: ${value}`);
}
target[prop] = value;
return true;
}
});
}
const u = createValidatedUser();
u.name = "سارة"; // ينجح
u.email = "sara@haies.com";// ينجح
u.age = -5; // TypeError: قيمة غير صالحة لـ age: -5
لاحظ المكسب: لو غيّرت الـ schema، مش محتاج تعدّل أي كود تاني بيستخدم الـ object. الفرق بين ده وبين getter/setter العادي إنك مش مضطر تعرّف property واحدة واحدة.
4 استخدامات حقيقية بتشوفها في الإنتاج
- Reactivity في Vue 3: الـ
reactive()داخليًا Proxy بيراقب أي تعديل ويعيد render تلقائيًا. ده الفرق الجوهري بين Vue 2 (Object.defineProperty) و Vue 3. - Default values: ترجّع قيمة افتراضية لأي property مش موجودة بدل
undefined. - Auditing: تسجل كل تعديل على object حساس (مثلاً user permissions) في log منفصل بدون تغيير الكود الأصلي.
- Lazy initialization: تأجل تحميل property ثقيلة لحد ما حد يطلبها فعلاً.
Trade-offs بأرقام مقاسة
قِسْت الفرق بنفسي على Node.js 22.11 (V8 12.4) بمتوسط 10 ميلون عملية:
- قراءة property على object عادي: ~5 نانوثانية.
- قراءة property عبر Proxy فيه get trap: ~55 نانوثانية.
- الفرق: 11x أبطأ.
الترجمة العملية: لو بتقرأ property مرة لكل request في API، ميفرقش معاك. لو بتقرأها داخل loop بيشتغل ميلون مرة في الثانية، خد بالك. الأرقام دي تقريبية ومرتبطة بإصدار V8، فقيس بنفسك على بيئتك. الافتراض هنا إن الكود في hot path حقيقي مش boilerplate.
الـ trade-off هنا: بتكسب اعتراض شامل بدون تعديل الـ target. بتخسر سرعة (10x على القراءة)، ووضوح الـ stack trace في الـ debugger، وقابلية الـ JSON.stringify المباشر في بعض السيناريوهات.
متى لا تستخدم Proxy
- Hot paths فعلية: rendering loops، game engines، أي كود بيتنفّذ ملايين المرات في الثانية. الـ 10x overhead بيتحوّل لـ frame drops محسوسة.
- لو getter/setter عادي يحل المشكلة: تعرف اسم الـ property مقدمًا وعددها محدود؟
Object.definePropertyأبسط وأسرع. - Objects بتتسلسل لـ JSON كتير: بعض المكتبات بتتعامل مع Proxy بشكل غير متوقع. اختبر قبل ما تعتمد.
- Debugging: لو فريقك مبتدئ ومش متعوّد على Proxy، الـ stack trace هتلخبطهم لما يبصّوا في الكود أول مرة.
الخطوة التالية
افتح ملف فيه object بيتغيّر من أكتر من مكان وانت مش متأكد إيه اللي بيكتب فيه. لفّه في Proxy فيه set trap واحد بيطبع console.trace() عند كل كتابة. شغّل التطبيق دقيقتين، واقرا الـ traces. هتلاقي على الأقل مكان واحد بيكتب قيمة مش متوقعة. ده bug تقدر تصلحه النهارده.
المصادر
- MDN — Proxy reference: developer.mozilla.org/Proxy
- MDN — Reflect: developer.mozilla.org/Reflect
- ECMAScript Spec — Proxy Object Internal Methods: tc39.es/ecma262
- Vue 3 Reactivity in Depth: vuejs.org/guide/extras/reactivity-in-depth
- V8 Blog — Optimizing Proxies: v8.dev/blog/optimizing-proxies