شرح الـ Proxies في JavaScript 🎯
مقدمة
هنتكلم عن ال Proxies وتعال أفاجئك بسر بسيط ، Vue 3 - الفريمورك الشهير - مبني على الـ Proxies!
ليه؟ عشان الـ Proxies بتدي أداء أحسن بكتير من الطريقة القديمة اللي كانت Vue بتستخدمها (Object.defineProperty).
يعني إيه Proxy؟ 🤔
تخيل إن الـ Proxy زي البودي جارد بتاع الـ object. أي حد عايز يتعامل مع الـ object لازم يمر من خلال البودي جارد الأول. ودا بيديك تحكم كامل في:
- قراءة الخصائص
- تعديل القيم
- حذف الخصائص
- واي حاجة تانية ممكن تتعمل على الـ object
ببساطة، Proxy هو Wrapper حوالين object عشان يديك قدرات متقدمة على الobjects.
الـ Syntax الأساسي ✍️
بص هتتوه وتحس الدنيا تقيلة وإنك مش فاهم بس ممكن تصبر لما توصل للكود وترجع تبص ع الsyntax هتفهم إي الtraps والحاجات الغريبة دي ، ف اصبر معايا للأخر بالله
إنشاء Proxy سهل ، دي الsyntax الأساسية
const proxy = new Proxy(target, handler);
- target: دا الـ object اللي عايز تحميه وتتحكم فيه
- handler: دا الـ object اللي فيه القواعد بتاعتك (الـ traps)
الـ Traps المشهورة 🎣
الـ traps دي زي الأفخاخ اللي بتمسك أي عملية حد عايز يعملها على الـ object
const handler = {
// لما حد يقرا خاصية
get(target, prop) { },
// لما حد يعدل أو يضيف خاصية
set(target, prop, value) { },
// لما حد يستخدم العملية in
has(target, prop) { },
// لما حد يحذف خاصية
deleteProperty(target, prop) { },
// لما حد يستدعي function
apply(target, thisArg, args) { },
// لما حد يستخدم new
construct(target, args) { }
}
- get بتتعامل مع property access
- set بتتعامل مع property assignment
- has بتتعامل مع operator ال ، in الـ in بيستخدم عشان يتحقق إذا كان الobject بيحتوي على خاصية معينة ولا لأ. يعني بيشوف إذا كانت الخاصية موجودة في الobject أو لا.
- deleteProperty بتتعامل مع حذف الخواص.
- apply بتتعامل مع استدعاء الدوال(function invocation).
- construct بتتعامل مع الـ new operator
أمثلة عملية 💡
تعال بقي واحدة واحدة نفك الألغاز دي إنشاء Proxy بسيط يلا نعمل Proxy بسيط عشان نسجل “property access” و ال “property assignment” لل object .
1️⃣ مثال بسيط GET و SET للـ Object properties
const target = { name: 'أحمد', age: 25 };
const handler = {
get(target, prop) {
console.log(`حد بيحاول يقرا الخاصية: ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`حد بيحاول يغير ${prop} للقيمة: ${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
// تجربة
console.log(proxy.name); // هيطبع: حد بيحاول يقرا الخاصية: أحمد
proxy.age = 26; // هيطبع: حد بيحاول يغير سن للقيمة: 26
طيب مش فاهم برضه أقدر أستفيد من الproxies إزاي ؟
ممكن نستخدمها لفرض قواعد الـ validation على الـobjects دا بيكون مفيد جداً لما بنتعامل مع user input أو API responses. بيفتح لينا طبقة جديدة من الـ validation!
2️⃣ مثال للـ Validation: التحقق من صحة البيانات
const userHandler = {
set(target, prop, value) {
if (prop === 'age' && value < 0) {
throw new Error('العمر لازم يكون أكبر من صفر!');
}
if (prop === 'email' && !value.includes('@')) {
throw new Error('الإيميل مش صح!');
}
target[prop] = value;
return true;
}
};
const user = new Proxy({}, userHandler);
// تجربة
try {
user.age = -5; // هيرمي error
} catch (e) {
console.log(e.message);
}
try {
user.email = 'test'; // هيرمي error
} catch (e) {
console.log(e.message);
}
ممكن نستخدمها لربط البيانات في الـ frameworks، بيسمح بالتحديث التلقائي للـ UI لما الـ underlying data تتغير. ودا اللي Vue بتعمله
هحطلك مثال بسيط وبعده كود advanced يعمل simulation للـvue
3️⃣ مثال للـ Data Binding
const data = {
text: 'Hello, world!'
};
const handler = {
set: function(target, property, value, receiver) {
target[property] = value;
document.getElementById('output').textContent = value;
return true;
}
};
const proxy = new Proxy(data, handler);
document.getElementById('input').addEventListener('input', function(event) {
proxy.text = event.target.value;
});
3️⃣Vue Simulation
متقلقش تحت شارحلك كل الدنيا دي ومقسمهالك أجزاء
const reactiveHandler = {
get(target, prop) {
track(target, prop); // Track dependencies
return target[prop];
},
set(target, prop, value) {
const oldValue = target[prop];
target[prop] = value;
if (oldValue !== value) {
trigger(target, prop); // Trigger updates
}
return true;
}
};
// Dependency tracking
const deps = new Map();
function track(target, prop) {
if (activeEffect) {
let depsForProp = deps.get(prop);
if (!depsForProp) {
depsForProp = new Set();
deps.set(prop, depsForProp);
}
depsForProp.add(activeEffect);
}
}
function trigger(target, prop) {
const depsForProp = deps.get(prop);
if (depsForProp) {
depsForProp.forEach(effect => effect());
}
}
// Create reactive data
let activeEffect = null;
const data = new Proxy({}, reactiveHandler);
// Watcher setup
function watch(callback) {
activeEffect = callback;
callback();
activeEffect = null;
}
// Usage example
watch(() => {
document.querySelectorAll(`[data-bind="name"]`)
.forEach(el => el.textContent = data.name);
});
// HTML: <div data-bind="name"></div>
data.name = 'محمد'; // Will automatically update the UI
تعال نفهم الكود دا كله بالتفصيل عشان الدنيا توضح وهنقسمه أجزاء طيب، الكود ده ببساطة بيعمل حاجة زي “ربط تفاعلي” (Reactive Binding) للبيانات، يعني لو أي قيمة جوا data اتغيرت، واجهة المستخدم (UI) بتتحدث أوتوماتيك من غير ما تحتاج تشغل functions معينة يدويًا.
const reactiveHandler = {
get(target, prop) {
track(target, prop); // تعقب التبعية
return target[prop];
},
set(target, prop, value) {
const oldValue = target[prop];
target[prop] = value;
if (oldValue !== value) {
trigger(target, prop); // تنشيط التحديثات
}
return true;
}
};
هنا عرفنا object الـ reactiveHandler اللي بيتحكم في عملية الـ get و set
get: بتشتغل أول ما تحاول تجيب قيمة خاصية من data. هنا بنستدعي track عشان نسجل إن فيه effect بيعتمد على الخاصية دي.
set: بتشتغل لما تيجي تغير قيمة خاصية معينة. هنا بيتأكد إذا كانت القيمة الجديدة مختلفة عن القديمة، ولو مختلفة بيشغل trigger لتحديث العناصر المرتبطة بيها.
// Dependency tracking
const deps = new Map();
function track(target, prop) {
if (activeEffect) {
let depsForProp = deps.get(prop);
if (!depsForProp) {
depsForProp = new Set();
deps.set(prop, depsForProp);
}
depsForProp.add(activeEffect);
}
}
هنا عندنا Map اسمها deps عشان نخزن التبعيات. دالة track بتضيف activeEffect لكل خاصية (prop) جوا data. الفكرة إن لو في effect بيستخدم خاصية معينة، بيتسجل جوة deps عشان لما الخاصية دي تتغير، الـ effect ده يتحدث.
function trigger(target, prop) {
const depsForProp = deps.get(prop);
if (depsForProp) {
depsForProp.forEach(effect => effect());
}
}
دالة trigger بتشغل كل effect المرتبط بالخاصية prop اللي حصل فيها تغيير. وده معناه إن لو في حاجة في الـ UI بتعتمد على الخاصية دي، هتتحدث تلقائيًا. إنشاء البيانات التفاعلية:
let activeEffect = null;
const data = new Proxy({}, reactiveHandler);
هنا بنستخدم Proxy عشان نعمل object data تفاعلي، بحيث أي تعديل على data بيشغل الـ set جوه reactiveHandler اللي شفناه فوق، وبالتالي الـ UI بيحصل له تحديث تلقائي.
function watch(callback) {
activeEffect = callback;
callback();
activeEffect = null;
}
function watch بتعملك مراقبة لحاجة معينة. هنا callback بيتعين في activeEffect وبيتنفذ، ولما يخلص، بنرجع activeEffect لـ null.
watch(() => {
document.querySelectorAll(`[data-bind="name"]`)
.forEach(el => el.textContent = data.name);
});
data.name = 'محمد';
هنا بنعمل watch لـ data.name. يعني لو فيه عنصر HTML فيه data-bind=“name”، النص بتاعه بيتحدث أول ما data.name تتغير.
4️⃣ مثال للـ Immutability: منع التعديل على الـ Object
const immutableHandler = {
set() {
throw new Error('مينفعش تعدل في الـ object دا!');
},
deleteProperty() {
throw new Error('مينفعش تمسح حاجة من الـ object دا!');
}
};
const config = new Proxy({
API_KEY: '123456',
BASE_URL: 'https://api.example.com'
}, immutableHandler);
// تجربة
try {
config.API_KEY = 'new-key'; // هيرمي error
} catch (e) {
console.log(e.message);
}
استخدامات عملية للـ Proxies 🚀
التحقق من صحة البيانات (Validation)
- التحقق من الـ input بتاع المستخدم
- التحقق من الـ API responses
- فرض قواعد معينة على البيانات
ربط البيانات (Data Binding)
- ربط الـ model بالـ view (زي Vue)
- تحديث الـ UI تلقائي
المراقبة والـ Debugging
- تتبع الـ property access
- تسجيل الـ modifications
- قياس الأداء
الحماية (Protection)
- منع التعديل على الـ objects المهمة
- التحكم في الوصول للخصائص
- تشفير/فك تشفير البيانات تلقائي
مفيدة جداً في الـ debugging والـ profiling. ليها استخدام كبير في application testing
نصائح مهمة 🎯
الأداء: الـ Proxies أسرع من Object.defineProperty، بس برضه متستخدمهاش على كل حاجة
Browser Support: الـ Proxies مدعومة في كل المتصفحات الحديثة
لا تنسى الـ return true: في الـ set trap، لازم ترجع true لو العملية نجحت
في النهاية 🎉
الـ Proxies أداة قوية جداً في JavaScript، وفهم قوتها هيخليك:
- تكتب كود أنضف
- تتحكم في الـ objects بشكل أفضل
- تفهم الـ frameworks الحديثة أكتر
وزي ما شوفنا، Vue 3 مخترتش الـ Proxies عبث - دي تكنولوجيا قوية وفعالة ومستقبلها واعد!