ما الذي يحدث عند تجميد النموذج الأولي (Prototype) في JavaScript؟ دليل شامل
هل تساءلت يوماً عن التداعيات البرمجية لتجميد النموذج الأولي (prototype) لكائن في JavaScript؟ هذا السؤال يحمل في طياته مفاهيم جوهرية تتعلق بكيفية عمل الوراثة وإدارة الخصائص في هذه اللغة الديناميكية. في هذا المقال، سنغوص معاً في تفاصيل هذه العملية، مستكشفين كيف يمكن لتجميد النموذج الأولي أن يغير من سلوك الكائنات التي ترث منه، وكيف يمكن لهذه الميزة أن تعزز من متانة وأمان تطبيقاتك.
مقدمة في الكائنات والنماذج الأولية (Prototypes) في JavaScript
الكائنات في JavaScript: أساسيات البناء
في JavaScript، الكائنات هي مجموعات ديناميكية من الخصائص، تتميز بوجود خاصية “خفية” (hidden property) تربطها بنماذجها الأولية. لنبدأ بإنشاء كائن بسيط باستخدام صيغة الكائن الحرفي (object literal syntax):
const counter = {
count : 0,
increment(){
this.count += 1;
return this.count;
},
decrement(){
this.count -= 1;
return this.count
}
}
console.log(counter.increment()) // 1
هنا، counter هو كائن يحتوي على حقل (field) باسم count ودالتين (methods) هما increment() و decrement() تعملان على هذا الحقل.
فهم النماذج الأولية (Prototypes) وآلية الوراثة
الكائنات في JavaScript يمكنها وراثة الخصائص من النماذج الأولية (prototypes). في الواقع، الكائن counter الذي أنشأناه للتو يرث بالفعل من الكائن Object.prototype. على سبيل المثال، يمكننا استدعاء الدالة toString() على الكائن counter حتى لو لم نقم بتعريفها بشكل صريح داخل counter:
console.log(counter.toString()); // [object Object]
أفضل طريقة للتعامل مع النماذج الأولية هي استخلاص الدوال المشتركة ووضعها في نموذج أولي واحد، ثم مشاركتها بين جميع الكائنات التي تتشارك نفس السلوك. دعونا نطبق ذلك باستخدام الدالة Object.create():
const counterPrototype = {
increment(){
this.count += 1;
return this.count;
},
decrement(){
this.count -= 1;
return this.count
}
}
const counter = Object.create(counterPrototype);
counter.count = 0;
console.log(counter.increment()); // 1
الدالة Object.create() تنشئ كائناً جديداً، مستخدمةً كائناً موجوداً كنموذج أولي (prototype) لهذا الكائن الجديد. في هذا المثال، الكائن counter أصبح لديه counterPrototype كنموذج أولي له.
مرونة النماذج الأولية: قوة وضعف
نظام النماذج الأولية في JavaScript مرن للغاية، ولكنه يحمل بعض الجوانب التي قد تكون سلبية. فجميع الخصائص تكون عامة (public) وقابلة للتغيير. على سبيل المثال، يمكننا إعادة تعريف تنفيذ الدالة increment() مباشرة في الكائن counter، حتى لو كانت موجودة في النموذج الأولي الخاص به:
const counter = Object.create(counterPrototype);
counter.count = 0;
counter.increment = function () {
console.log('increment called from counter object');
return "custom increment";
}
console.log(counter.increment()); // "increment called from counter object"
في هذا السيناريو، يتم تجاوز الدالة increment() الموجودة في النموذج الأولي counterPrototype بدالة جديدة معرفة مباشرة في الكائن counter. هذا السلوك يعكس مرونة JavaScript ولكنه قد يؤدي إلى تعديلات غير مقصودة أو غير مرغوبة في بعض الحالات.
تجميد النماذج الأولية (Freezing Prototypes): حماية الخصائص
لنرى الآن ما الذي يحدث إذا قمنا بتجميد النموذج الأولي. تجميد الكائن يمنعنا من إضافة، إزالة، أو تغيير خصائصه. نستخدم الدالة Object.freeze() لتحقيق ذلك:
const counterPrototype = Object.freeze({
increment(){
this.count += 1;
return this.count;
},
decrement(){
this.count -= 1;
return this.count
}
});
// محاولة تغيير دالة increment() في النموذج الأولي المجمد
try {
counterPrototype.increment = function () {
console.log('increment attempt on frozen prototype');
}
} catch (e) {
console.error("خطأ: ", e.message); // "Cannot assign to read only property 'increment' of object '#
كيف يعمل Object.freeze()؟
الدالة Object.freeze() تقوم بتجميد كائن. الكائن المجمد لا يمكن تعديله بعد الآن. لا يمكننا إضافة خصائص جديدة إليه، أو تعديل خصائصه الحالية، أو إزالة أي منها. أي محاولة للقيام بذلك ستفشل بصمت في الوضع العادي (non-strict mode) أو ستلقي خطأ (TypeError) في الوضع الصارم (strict mode).
تأثير تجميد النموذج الأولي على الكائنات الوارثة
الآن، دعونا نلقي نظرة على ما يحدث عند محاولة تغيير دالة في الكائن counter الذي يرث من counterPrototype المجمد:
const counter = Object.create(counterPrototype);
counter.count = 0;
// محاولة تغيير دالة increment() في الكائن الوارث
try {
counter.increment = function () {
console.log('increment called from counter object after prototype freeze');
return "custom increment after freeze";
}
} catch (e) {
console.error("خطأ في التعيين: ", e.message); // "Cannot assign to read only property 'increment' of object '#
كما ترون، بعد تجميد النموذج الأولي counterPrototype، وعند محاولة تعيين دالة increment() جديدة مباشرة للكائن counter، تفشل عملية التعيين هذه (وتلقي خطأ TypeError في الوضع الصارم). هذا لأن Object.freeze() يجعل خصائص النموذج الأولي غير قابلة للكتابة (non-writable) وغير قابلة للتهيئة (non-configurable). عندما يحاول الكائن counter تعيين خاصية بنفس اسم الخاصية الموجودة في النموذج الأولي المجمد، فإنه يفشل لأن النموذج الأولي يمنع ذلك. وبالتالي، عند استدعاء counter.increment()، يتم استدعاء الدالة الأصلية الموجودة في النموذج الأولي المجمد، ويظل السلوك المشترك المعرف في النموذج الأولي ثابتاً وغير قابل للتعديل من قبل الكائنات الوارثة.
ملخص لأهم النقاط
- الكائنات في JavaScript لديها خاصية خفية تشير إلى نموذجها الأولي (
prototype). - يُستخدم النموذج الأولي عادةً للاحتفاظ بالدوال والخصائص المشتركة بين الكائنات المختلفة.
- تجميد النموذج الأولي باستخدام
Object.freeze()يمنع تعديل، إضافة، أو حذف خصائصه. - عندما يتم تجميد النموذج الأولي، لا يمكن للكائنات التي ترث منه تجاوز (
override) الخصائص الموروثة التي أصبحت غير قابلة للكتابة. هذا يوفر طبقة من الثبات والحماية للخصائص المشتركة.
الخلاصة التقنية
تجميد النموذج الأولي في JavaScript باستخدام Object.freeze() هو أداة قوية لفرض الثبات (immutability) على الخصائص والدوال المشتركة. بينما توفر النماذج الأولية مرونة كبيرة في الوراثة وتشارك السلوك، فإن هذه المرونة قد تؤدي أحيانًا إلى تعديلات غير مقصودة. من خلال تجميد النموذج الأولي، نضمن أن الخصائص الموروثة تظل ثابتة وغير قابلة للتعديل من قبل الكائنات الوارثة، مما يعزز من موثوقية الكود ويقلل من الأخطاء المحتملة. هذه الممارسة مفيدة بشكل خاص في بناء أنظمة معقدة تتطلب سلوكيات ثابتة ومحددة جيدًا عبر تسلسل الكائنات.