الثبات في JavaScript: شرح الكائنات المجمّدة Object.freeze مع أمثلة عملية
في لغة JavaScript نستخدم Object لتخزين عدة قيم داخل بنية بيانات واحدة ومنظمة. يُنشأ الكائن عادةً باستخدام الأقواس المعقوفة {}، ويمكن أن يحتوي على خاصية واحدة أو عدة خصائص. كل خاصية تتكون من زوج key-value، حيث يكون المفتاح نصاً أو من نوع Symbol، بينما يمكن أن تكون القيمة من أي نوع، بما في ذلك كائن آخر.
لنبدأ بمثال بسيط يوضح شكل الكائن:
const user = {
name: 'Bob',
age: 27
};
في المثال السابق أنشأنا كائناً باسم user يحتوي على خاصيتين: name وage. ورغم أننا استخدمنا الكلمة المفتاحية const، فهذا لا يعني أن محتوى الكائن أصبح ثابتاً بالكامل.
افتراضياً، الكائنات في JavaScript تكون mutable، أي قابلة للتغيير بعد إنشائها. هذا يعني أنه يمكن:
- إضافة خصائص جديدة.
- تعديل قيم الخصائص الحالية.
- حذف خصائص موجودة.
وهنا يظهر مفهوم immutability أو الثبات. عندما يكون الكائن ثابتاً، لا يمكنك تعديله برمجياً بعد إنشائه، لا بالإضافة ولا بالحذف ولا بالتحديث.
ما المقصود بثبات الكائنات في JavaScript؟
الثبات يعني ببساطة أن البيانات لا تتغير مع مرور الوقت أثناء تشغيل البرنامج. إذا كان الكائن immutable، فإن حالته الداخلية تبقى كما هي بعد إنشائه. هذا الأسلوب مهم جداً عندما تريد حماية البيانات الحساسة أو القيم المرجعية التي يجب أن تبقى ثابتة داخل التطبيق.
ومن هنا يأتي مفهوم Frozen Object، أي الكائن المجمّد، وهو كائن لا يمكن توسيعه أو تعديل خصائصه أو حذفها.
كيفية إنشاء كائن مجمّد باستخدام Object.freeze()
توفّر JavaScript الدالة Object.freeze(obj) لجعل الكائن غير قابل للتعديل. هذه الدالة تعيد نفس الكائن بعد تجميده.
لننشئ كائناً يضم اللغات المدعومة:
const supportedLanguages = {
af: 'Afrikaans',
bn: 'Bengali',
de: 'German',
en: 'English',
fr: 'French'
};
إذا كنت لا تريد لهذا الكائن أن يتغير بعد إنشائه، يمكنك تجميده كالتالي:
const frozenSupportedLanguages = Object.freeze(supportedLanguages);
// الكائنان يشيران إلى نفس المرجع
console.log(frozenSupportedLanguages === supportedLanguages); // true
بعد ذلك، أي محاولة لتعديل الكائن لن تنجح:
// إضافة خاصية جديدة
supportedLanguages['kn'] = 'Kannada';
// تعديل خاصية موجودة
supportedLanguages['af'] = 'something else';
// حذف خاصية
delete supportedLanguages.bn;
console.log(supportedLanguages);
في الوضع الصارم strict mode قد تظهر أخطاء صريحة عند تنفيذ هذه المحاولات، أما في بعض الحالات الأخرى فقد تفشل العملية بصمت دون تعديل فعلي للكائن.
هل const يساوي Object.freeze()؟
هذا من أكثر الأسئلة شيوعاً في المقابلات التقنية، والإجابة المختصرة هي: لا.
استخدام const يمنع فقط إعادة إسناد المتغير إلى قيمة جديدة، لكنه لا يمنع تعديل الكائن نفسه إذا كان المتغير يشير إلى كائن.
لنفهم الفرق عملياً:
const supportedLanguages = {
af: 'Afrikaans',
bn: 'Bengali',
de: 'German',
en: 'English',
fr: 'French'
};
يمكنك الآن تنفيذ التعديلات التالية بنجاح:
supportedLanguages['kn'] = 'Kannada';
supportedLanguages['af'] = 'something else';
delete supportedLanguages.bn;
console.log(supportedLanguages);

لكن إذا حاولت إعادة إسناد قيمة جديدة بالكامل إلى المتغير supportedLanguages:
supportedLanguages = {
id: 'Indonesian'
};
فستحصل على خطأ لأن const يمنع تغيير المرجع نفسه.

إذن الفرق واضح:
const: يمنع إعادة الإسناد.Object.freeze(): يمنع تعديل الكائن نفسه.
لماذا نحتاج إلى الكائنات المجمّدة؟
نحتاج إلى الكائنات المجمّدة عندما نرغب في فرض الثبات على البيانات. هذا السيناريو مفيد جداً في التطبيقات الكبيرة، خصوصاً عندما تتعامل مع إعدادات أو ثوابت لا يجب أن تتغير أثناء التشغيل.
أهم الاستخدامات العملية
- حماية كائنات الإعدادات
configuration objects. - تثبيت مجموعة لغات أو صلاحيات أو قيم ثابتة داخل التطبيق.
- تقليل الأخطاء الناتجة عن التعديل غير المقصود.
- تحسين وضوح تدفق البيانات في البرمجة الوظيفية
functional programming.
في بعض اللغات مثل Java توجد مفاهيم مشابهة مثل final، وفي Kotlin توجد تراكيب تميل إلى الثبات افتراضياً في بعض الحالات. أما في JavaScript فالأمر يتطلب غالباً قراراً صريحاً من المطور.
تجميد المصفوفات في JavaScript
المصفوفات Arrays هي في الأصل كائنات في JavaScript، لذلك يمكن أيضاً تطبيق Object.freeze() عليها.
لنأخذ مثالاً لمصفوفة تمثل الحواس البشرية:
const senses = ['touch', 'sight', 'hearing', 'smell', 'taste'];
يمكن تجميدها هكذا:
Object.freeze(senses);
بعد التجميد، لن تتمكن من إضافة عنصر جديد باستخدام push():
senses.push('walking');

وكذلك لن تتمكن من حذف عنصر باستخدام pop():
senses.pop();

السبب أن المصفوفة بعد التجميد تصبح غير قابلة للتمديد not extensible، وبالتالي لا يمكن إضافة عناصر جديدة أو حذف عناصر حالية منها بالطريقة المعتادة.
تجميد الكائنات في JavaScript هو تجميد سطحي
من المهم جداً معرفة أن Object.freeze() يطبق ما يسمى shallow freeze، أي التجميد السطحي فقط. هذا يعني أن الخصائص الموجودة في المستوى الأعلى من الكائن تصبح ثابتة، لكن إذا كانت إحدى القيم نفسها كائناً آخر، فلن يتم تجميد هذا الكائن الداخلي تلقائياً.
لنفهم ذلك من خلال كائن إعدادات:
const config = {
db: 'postgresql',
host: 'acme-ind.com',
password: 'fake-password',
port: 512,
admin: {
name: 'Tapas',
rights: ['create', 'update', 'delete']
}
};
نجمّد الكائن:
Object.freeze(config);
الآن إذا حاولت تعديل الخاصية db فلن ينجح الأمر:
config.db = 'redis';
لكن يمكنك رغم ذلك تعديل الكائن المتداخل داخل admin:
config.admin.name = 'atapas';
لأن الكائن الداخلي لم يُجمَّد فعلياً.

كيفية تنفيذ deep freeze على الكائنات
إذا كنت تحتاج إلى تجميد جميع المستويات داخل الكائن، فعليك تطبيق ما يُعرف بـ deep freeze. الفكرة هنا هي المرور على كل خاصية، وإذا كانت قيمتها كائناً آخر، يتم استدعاء الدالة نفسها بشكل递ي أو تكراري recursion حتى الوصول إلى أعمق مستوى.
إليك دالة عملية لتنفيذ ذلك:
const deepFreeze = obj => {
Object.keys(obj).forEach(prop => {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
};
deepFreeze(config);
هذه المقاربة مفيدة عندما يحتوي الكائن على مستويات متداخلة كثيرة، وتريد ضمان عدم تعديل أي جزء منها أثناء تشغيل التطبيق.
الفرق بين freeze() وseal() وpreventExtensions()
لا تقتصر أدوات التحكم في قابلية تعديل الكائنات على Object.freeze() فقط. توجد أيضاً دالتان مهمتان توفران درجات أقل من التقييد:
Object.seal(): يمنع إضافة خصائص جديدة أو حذف خصائص موجودة، لكنه يسمح بتحديث قيم الخصائص الحالية.Object.preventExtensions(): يمنع إنشاء خصائص جديدة، لكنه يسمح بتعديل الخصائص الحالية وحذفها.
المقارنة التالية توضّح الفرق بشكل عملي:
| العملية | freeze |
seal |
preventExtensions |
|---|---|---|---|
| إنشاء خاصية جديدة | ❌ | ❌ | ❌ |
| قراءة الخصائص | ✔️ | ✔️ | ✔️ |
| تحديث خاصية موجودة | ❌ | ✔️ | ✔️ |
| حذف خاصية | ❌ | ❌ | ✔️ |
إذا كنت تريد أعلى مستوى من الثبات، فاستخدم Object.freeze(). أما إذا كنت تريد تقييداً جزئياً، فقد تكون الدالتان الأخريان أنسب حسب السيناريو.
هل يمكن إلغاء تجميد كائن مجمّد؟
حتى الآن، لا توجد طريقة مباشرة في اللغة لإلغاء التجميد بعد تنفيذ Object.freeze(). وبمعنى أدق، لا توجد دالة مثل unfreeze() في JavaScript.
الحل العملي المعتاد هو إنشاء نسخة جديدة من الكائن، ثم العمل على هذه النسخة بدلاً من الأصل. لكن يجب الانتباه إلى أن النسخ السطحي shallow copy قد لا يكون كافياً إذا كانت البيانات متداخلة.
أفضل ممارسات استخدام الثبات في JavaScript
متى يُفضّل استخدام التجميد؟
- عند تعريف إعدادات ثابتة خاصة بالتطبيق.
- عند التعامل مع قوائم مرجعية لا ينبغي تعديلها.
- في الأنظمة الكبيرة التي يعمل عليها أكثر من مطور.
- في الشيفرات التي تعتمد على النقاء والوضوح مثل تطبيقات البرمجة الوظيفية.
متى قد لا يكون الخيار الأفضل؟
- إذا كنت تحتاج إلى تحديث متكرر للكائنات أثناء التشغيل.
- إذا كان الأداء حساساً جداً مع كائنات متداخلة وضخمة.
- إذا كانت بنية البيانات مصممة أصلاً للتعديل المستمر.
ملخص سريع لأهم النقاط
- تستخدم
Object.freeze()لجعل الكائن غير قابل للتعديل. - لا يمكن بعد التجميد إضافة خصائص أو تعديلها أو حذفها.
constلا يساوي التجميد، بل يمنع فقط إعادة الإسناد.- يمكن تجميد المصفوفات أيضاً لأنها كائنات في الأساس.
- التجميد الافتراضي سطحي
shallowوليس عميقاً. - يمكن تنفيذ
deep freezeباستخدامrecursion. - الدالتان
seal()وpreventExtensions()تقدمان مستويات جزئية من التقييد. - لا توجد طريقة أصلية مباشرة لإلغاء التجميد.
الخلاصة التقنية
إذا كنت تبني تطبيقاً يعتمد على سلامة البيانات وقابلية التنبؤ بالسلوك، فإن فهم الثبات في JavaScript ليس أمراً اختيارياً. استخدام Object.freeze() يمنحك طبقة مهمة من الحماية ضد التعديلات غير المقصودة، لكنه لا يكفي وحده مع الكائنات المتداخلة ما لم تطبّق deep freeze. من الناحية العملية، القرار الأفضل هو اختيار درجة الثبات المناسبة حسب طبيعة البيانات، مع الموازنة بين الأمان والمرونة والأداء.