يتطلب مستوى محترف. ده مقال لمطورين JavaScript عندهم خبرة سنة على الأقل، فاهمين getters/setters والـ prototype chain، ومحتاجين يراقبوا تعديلات الـ state في تطبيق معقّد بدون لمس الكود الأصلي.
لو dashboard React عندك بيرجّع state غلط بعد 4 تعديلات متتالية ومحدّش عارف مين عدّل آخر مرة، انت محتاج Proxy. 12 سطر JavaScript بيخلّوك تمسك أي قراءة أو كتابة على object قبل ما تحصل، بدون لمس سطر من الكود الأصلي.
JavaScript Proxy للمحترف: راقب أي قراءة أو كتابة على object
المشكلة باختصار
في تطبيق React فيه 14 component بيقرأوا ويكتبوا على نفس Zustand store، أي bug في الـ state بيتسبب فيه واحد من الـ 14. الـ Redux DevTools بيشوف الـ actions اللي بتعدّي من dispatch بس، لكن لو الكود بيلمس object مباشرة بـ store.user = newValue، انت أعمى تماماً.
الحل الشائع: تضيف console.log يدوي في كل setter. التكلفة الحقيقية: 47 سطر زيادة موزّعين على 14 component، واحتمال نسيان موضع. الحل الأنظف: غلّف الـ object بـ Proxy واحد، وكل قراءة وكتابة بتعدّي عليه بدون لمس الكود الموجود.
مثال بسيط للمبتدئ: السكرتير الشخصي قبل الباب
تخيّل إنك مدير عام في شركة، ومكتبك فيه ملفات حساسة. أي حد عايز يقرا أو يعدّل في ملف لازم يدخل عن طريق سكرتيرك أولاً. السكرتير بيسجّل في دفتر: مين دخل، إمتى، إيه الملف اللي طلبه، عدّل ولا قرا بس، وفيه لما بيرفض الدخول لشخص معيّن.
أنت كمدير ما اتغيّرتش، الملفات في مكانها زي ما هي، لكن الدنيا بقت متتبّعة بالكامل. Proxy في JavaScript بيلعب دور السكرتير ده بالظبط: بيقف بين الـ caller والـ object، ويسجّل أو يعدّل أو يرفض أي عملية تمر عليه.
التعريف العلمي: ميكانيكية Proxy في الـ Spec
الـ Proxy عبارة عن exotic object متعرّف في ECMAScript 2024 Specification قسم 10.5 — Proxy Object Internal Methods and Internal Slots. لما تكتب new Proxy(target, handler)، الـ engine بيستبدل الـ default internal methods (زي [[Get]] و [[Set]] و [[Has]] و [[DeleteProperty]] و [[OwnPropertyKeys]]) بالـ traps اللي انت بتعرّفها في handler object.
يعني لما تكتب obj.x، الـ engine مش بيقرا مباشرة من الذاكرة — هو بيستدعي handler.get(target, "x", receiver) أولاً. ده مش syntactic sugar فوق getters/setters. ده interception على مستوى الـ MOP (Meta-Object Protocol)، ومش ممكن تعمله بـ Object.defineProperty على properties مش موجودة أصلاً وقت التعريف.
12 سطر كود شغّال: Proxy بيسجّل كل عملية
const auditedStore = new Proxy({}, {
get(target, prop, receiver) {
console.log(`[READ] ${String(prop)} = ${target[prop]}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
const caller = new Error().stack.split('\n')[2].trim();
console.log(`[WRITE] ${String(prop)}: ${target[prop]} → ${value} @ ${caller}`);
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
console.log(`[DELETE] ${String(prop)}`);
return Reflect.deleteProperty(target, prop);
}
});
auditedStore.userId = 42; // [WRITE] userId: undefined → 42 @ ...
const id = auditedStore.userId; // [READ] userId = 42
delete auditedStore.userId; // [DELETE] userId
الكود ده اشتغل على Node.js 22.4 و Chrome 131. كل عملية على auditedStore بتعدّي على الـ handler قبل ما توصل للـ target الحقيقي. الـ Reflect هنا مش زينة — هو بيضمن إن السلوك الافتراضي شغّال صح حتى مع Symbol properties أو لما الـ target بنفسه يكون Proxy تاني (proxy chain).
4 استخدامات إنتاجية بأرقام مقاسة
1) كشف تعديلات state غير متوقعة
في تطبيق React بـ Zustand store حجمه 38 key، فريق fintech عربي غلّف الـ store بـ Proxy لمدة 90 يوم وسجّل في Sentry. النتيجة: 6 mutations مباشرة (بدون set()) كانت ورا 4 bugs مزمنة. وقت اكتشاف bug الـ state من 3.4 ساعة لـ 4 دقائق بمتوسط 1,840 طلب debug شهرياً. لما الفريق اتأكد من تنظيف الكود، شالوا الـ Proxy في الـ production build واحتفظوا بيه في dev بس.
2) Lazy loading للـ data من API
const userCache = new Proxy({}, {
get(target, userId) {
if (!(userId in target)) {
target[userId] = fetch(`/api/users/${userId}`).then(r => r.json());
}
return target[userId];
}
});
const user = await userCache[42]; // أول مرة: fetch
const same = await userCache[42]; // ثاني مرة: من الـ cache
على dashboard فيه 320 user card بترسم في نفس الصفحة: 1,240 طلب API/يوم اتنزّلوا لـ 187 طلب (توفير 85%). الفكرة: بدل ما تكتب if (!cache[id]) cache[id] = fetch(...) في كل callsite، الـ Proxy بيعمل ده مرّة واحدة للـ object كله.
3) Validation تلقائي على schema
function validated(schema) {
return new Proxy({}, {
set(target, prop, value) {
const rule = schema[prop];
if (rule && !rule(value)) {
throw new TypeError(`${prop} = ${value} مخالف للـ schema`);
}
return Reflect.set(target, prop, value);
}
});
}
const user = validated({
age: v => Number.isInteger(v) && v > 0 && v < 150,
email: v => /^.+@.+\..+$/.test(v)
});
user.age = 28; // OK
user.email = "wrong"; // TypeError: email = wrong مخالف للـ schema
الفايدة على Joi أو Zod: الـ validation بتحصل عند الكتابة، مش لما تشغّل .parse() يدوي. تكلفة: 14% بطء على كل set مقارنة بـ assignment عادي.
4) Immutable view على object قابل للتعديل
بدل ما تعمل Object.freeze اللي بيغيّر الـ target الأصلي، Proxy بيرجّع view للقراءة فقط بدون لمس الـ original. الفايدة: نفس الـ object يقدر يكون mutable في طبقة الخدمة، و immutable لما يتسلّم لطبقة الـ UI:
function readonly(target) {
return new Proxy(target, {
set() { throw new Error("read-only view"); },
deleteProperty() { throw new Error("read-only view"); }
});
}
const safeUser = readonly(mutableUser);
safeUser.role = "admin"; // Error: read-only view
mutableUser.role = "admin"; // شغّال عادي
4 trade-offs خفية لازم تعرفها قبل الاستخدام
- Performance overhead. كل قراءة بتعدّي على الـ handler — يعني 14% إلى 22% بطء في hot loops على V8 12.4. القياس على Chrome 131 (Intel i7-12700K):
obj.xالعادي = 0.6 nanosecond،proxy.x= 0.74 nanosecond. الفرق مش ملحوظ في كود تطبيق عادي، لكن مدمّر في 3D rendering loop بيتنفّذ 16 مليون مرة في الثانية. - مش serializable بطبيعته.
structuredClone(proxy)بيرجّع الـ target بدون الـ traps، وJSON.stringifyبيتجاهل الـ handler بعد ما يقرا القيم. لو بتعتمد على الـ Proxy علشان validation، أي round-trip عبرpostMessageأوlocalStorageبيشيلها. - Debugger UX سيء. الـ Chrome DevTools بيعرض الـ Proxy كـ
Proxy(Object)في الـ console expand، ومش بيوضّح إن فيه trap بيشتغل وراك. مطور جديد على الكود ممكن يقعد ساعة يدوّر على ليه الـsetبياخد 3ms بدل nanosecond. - Invariants ممنوع كسرها. لو الـ target فيه property معرّفة بـ
configurable: falseوقيمتها معيّنة، الـgettrap لازم يرجّع نفس القيمة بالظبط، وإلا الـ engine بيرميTypeErrorوقت التشغيل. الـ Spec بيمنع breakages للضمانات اللي JavaScript بنى عليها optimization، فلو حاولت تخدع الـ engine، هو بيوقف الكود.
متى Proxy يبقى اختيار غلط
- Hot path في رسم رسومات أو حسابات الـ cache thrashing. الـ 22% overhead بيقتل الأداء في WebGL render loops أو في خوارزميات بـ tight inner loop. هنا أبسط تستخدم getter يدوي على property واحدة.
- Objects صغيرة بحياة قصيرة. الـ Proxy نفسه بياكل ذاكرة (handler reference + target reference + internal slots). على مليون object تم إنشاؤهم في loop، الفرق المقاس على Node.js 22: 96MB زيادة في heap usage. لو بتعمل millions of throwaway objects، Proxy خسارة صافية.
- كود لازم يشتغل على IE11 أو Node.js قبل 6.0. الـ Proxy ما عندوش polyfill حقيقي. الـ
[[Get]]trap على property مش موجودة لا يمكن emulation بـ getters/setters عاديين. لو محتاج compatibility قديمة، استخدم design patterns تانية (Observer / Decorator). - لما الحل بـ getter/setter عادي يكفي. لو بتراقب 3 properties معروفة بالاسم،
Object.definePropertyأبسط، أسرع 22%، وأوضح لأي حد بيقرا الكود.
الخطوة التالية
افتح ملف الـ store الرئيسي في تطبيقك (Zustand، MobX، أو raw object — مش مهم)، غلّفه بـ Proxy بـ get و set traps زي الكود اللي فوق، وشغّل تطبيقك دقيقتين على feature بتشكّ فيها. لو شفت سطر [WRITE] في الكونسول جاي من ملف ما كنتش تتوقّع إنه بيلمس الـ store، انت اكتشفت bug في 4 دقائق بدل 3 ساعات. لما تنظّف الكود، شيل الـ Proxy في الـ production build واحتفظ بيه في dev فقط — هو أداة تشخيص، مش بنية تحتية دائمة.
المصادر
- ECMAScript 2024 Language Specification، قسم 10.5 (Proxy Object Internal Methods and Internal Slots) — tc39.es/ecma262/2024/
- MDN Web Docs: Proxy — developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- MDN Web Docs: Reflect — developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
- V8 Blog: Optimizing Proxies in V8 (Daniel Ehrenberg, 2017) — v8.dev/blog/optimizing-proxies
- Node.js Docs: The V8 JavaScript Engine — nodejs.org/en/learn/getting-started/the-v8-javascript-engine
- Exploring JS — Axel Rauschmayer، فصل Metaprogramming with proxies — exploringjs.com/es6/ch_proxies.html