الكائن: حقيبة من القيم المسمّاة
المصفوفة تجمع القيم حسب ترتيبها، أما الكائن (object) في جافا سكريبت فيجمعها حسب اسم كل قيمة. فحين يكون الشيء الذي تمثّله يتكوّن من أجزاء لها أسماء واضحة — مستخدم له اسم وعمر، أو طلب له method و URL — فالكائن هو الخيار المناسب.
وأبسط طريقة لإنشاء كائن في جافا سكريبت هي عبر الـ object literal:
كل عنصر عبارة عن زوج من النوع key: value. المفاتيح في الحقيقة عبارة عن سلاسل نصية تحت الغطاء (وعلامات الاقتباس اختيارية إذا كان المفتاح اسمًا صالحًا كمعرّف مثل name). أما القيم فيمكن أن تكون أي شيء تعرفه جافا سكريبت: أرقام، نصوص، قيم منطقية، مصفوفات، دوال، وحتى كائنات أخرى.
لا مشكلة في ترك فاصلة بعد آخر عنصر، بل إن معظم الفرق تُبقيها موجودة لأنها تُبقي الـ diff نظيفًا عند إضافة خاصية جديدة لاحقًا.
قراءة خصائص الكائن والكتابة إليها
هناك طريقتان للوصول إلى خاصية داخل الكائن: صيغة النقطة (dot notation) وصيغة الأقواس المربعة (bracket notation).
صيغة النقطة (.) أنظف وأجمل، وغالبًا ما ستلجأ إليها بشكل افتراضي. أما صيغة الأقواس المربعة ([]) فتُثبت فائدتها عندما يكون اسم الخاصية مخزَّنًا داخل متغير، أو عندما لا يكون اسمًا صالحًا كمُعرِّف:
قراءة خاصية غير موجودة ترجع undefined دون أي خطأ:
هذا يتجاهل الأخطاء الإملائية بصمت ويخفيها عنك. لو كنت تريد أن يصرخ الكود في وجهك عند وقوع خطأ، فعليك فحص ذلك يدويًا.
إضافة وحذف الخصائص في كائنات جافا سكريبت
الكائنات في جافا سكريبت مرنة ومفتوحة دائمًا — يمكنك إضافة خصائص جديدة في أي وقت تشاء:
ويمكن حذفها باستخدام delete:
delete من العوامل اللي ما راح تستخدمها كل يوم، لكن لما تحتاج تشيل مفتاح بشكل كامل من الكائن (مش مجرد تعطيه القيمة undefined)، فهو الأداة المناسبة. لأنك لو كتبت user.email = undefined، المفتاح يظل موجودًا — وتعبير "email" in user راح يرجّع true.
كيف تتحقق من وجود خاصية في كائن جافا سكريبت
في ثلاث طرق، وكل وحدة فيها لها معنى مختلف قليلًا:
inيفحص إن كان المفتاح موجودًا، بما في ذلك المفاتيح الموروثة من سلسلة الـ prototype.Object.hasOwn(obj, key)يفحص المفاتيح الخاصة بالكائن نفسه فقط، وهو ما تحتاجه عندما تريد تجاهل الخصائص الموروثة. هذه الطريقة جاءت بديلًا عنhasOwnPropertyالقديمة.obj.key !== undefinedيؤدي الغرض في أغلب الحالات، لكنه يخدعك إذا كانت قيمة الخاصية مضبوطة صراحةً علىundefined.
إن لم تكن متأكدًا، اختر Object.hasOwn فهو الأقرب عادةً لما تقصده فعلًا.
دوال الكائن: توابع تعيش داخل الكائن نفسه
أي خاصية قيمتها دالة تُسمى تابعًا (method). وهناك صيغة مختصرة لتعريف هذه التوابع داخل الـ object literal مباشرةً:
الكلمة المفتاحية this داخل الـ method تشير إلى الكائن الذي استُدعيت منه الدالة — أي user في مثالنا. وهكذا يعرف greet اسم مَن يستخدم.
لكن انتبه لنقطة مهمة: الدوال السهمية (arrow functions) ليس لها this خاص بها، لذا فهي ليست خياراً مناسباً للـ methods التي تحتاج للوصول إلى خصائص أخرى داخل نفس الكائن:
استخدم الصياغة المختصرة للدوال (greet() { ... }) مع أي شيء يستخدم this. أما الدوال السهمية (arrow functions) فهي ممتازة للـ callbacks، لكنها ليست الخيار المناسب لدوال الكائن.
الكائنات المتداخلة
القيم نفسها يمكن أن تكون كائنات، وبأي عمق تريده:
قراءة خاصية متداخلة تتمّ عبر التسلسل — user.address.city. لكن المشكلة: إذا كانت أي حلقة في هذه السلسلة null أو undefined، سيظهر لك TypeError:
console.log(user.profile.city);
// TypeError: Cannot read properties of undefined (reading 'city')
السلسلة الاختيارية (user.profile?.city) هي الحل العصري لهذه المشكلة — فهي تُرجع undefined بدلاً من رمي خطأ عندما تكون إحدى الحلقات مفقودة. وسنخصص لها صفحة مستقلة لاحقاً في هذا الفصل.
المرور على كائن جافا سكريبت
عندما تحتاج إلى المرور على كل مفاتيح الكائن، فإن مجموعة Object.keys وObject.values وObject.entries هي ما تبحث عنه:
Object.entries هي الأكثر فائدة من الجميع، لأنها تعطيك المفتاح والقيمة معًا في آنٍ واحد، وتنسجم بشكل ممتاز مع تفكيك المصفوفات (array destructuring).
هناك أيضًا حلقة for...in، لكنها تمر على الخصائص الموروثة كذلك، وهذا عادةً ليس ما تريده:
for (const key in scores) {
console.log(key); // يعمل، لكنه يشمل المفاتيح الموروثة
}
فضّل استخدام Object.keys أو Object.entries إلا إذا كنت تحتاج فعلاً إلى الخصائص الموروثة.
اختصارات في بناء الجملة تستحق المعرفة
هناك طريقتان مختصرتان ستصادفهما في كل مكان تقريباً:
الاختصار في الخصائص (property shorthand) يشتغل لما يكون اسم المتغيّر مطابقًا لاسم المفتاح. أما المفاتيح المحسوبة (صيغة [expr]) فتسمح لك ببناء أسماء الخصائص ديناميكيًا، وهي مفيدة جدًا عند كتابة دوال تُحدّث حقلًا معيّنًا بالاسم.
المقارنة بين الكائنات تتم بالمرجع
هذه النقطة يقع فيها الجميع تقريبًا مرّة واحدة على الأقل:
عامل المقارنة === عند تطبيقه على الكائنات يتحقق مما إذا كان الطرفان يشيران إلى نفس الكائن في الذاكرة، وليس ما إذا كانا يحملان نفس المحتوى. فكائنان لهما نفس الخصائص تمامًا يبقيان كائنَين مختلفَين.
إذا أردت التحقق من "هل يملكان نفس البنية؟"، فعليك مقارنة الحقول يدويًا، أو الاستعانة بدالة مساعدة للمقارنة العميقة (deep equality). الحيلة السريعة JSON.stringify(a) === JSON.stringify(b) قد تفي بالغرض مع البيانات البسيطة، لكنها تنهار أمام أي شيء يحتوي على دوال أو قيم undefined أو مراجع دائرية.
الكائنات قابلة للتعديل حتى مع const
كلمة const تُثبّت المتغير على قيمة واحدة فقط، لكنها لا تُجمّد الكائن الذي يشير إليه ذلك المتغير:
إذا كنت تريد فعلاً كائناً غير قابل للتعديل، يمكنك استخدام Object.freeze(user) لمنع أي تغييرات لاحقة (لكن التجميد سطحي فقط — الكائنات المتداخلة تبقى قابلة للتعديل). عملياً، معظم الأكواد تعتمد على العُرف بدلاً من Object.freeze، فتعامل مع كائنات const على أنها "لا تُعيد إسناد هذا المرجع" واضبط مسألة الثبات على مستوى التصميم.
ما التالي؟ المصفوفات
الكائنات والمصفوفات هما اللبنتان الأساسيتان اللتان يُبنى عليهما كل شيء آخر. الكائنات تتعامل مع البيانات المُعنوَنة، والمصفوفات تتعامل مع البيانات المرتّبة. في الدرس القادم: كيف تعمل المصفوفات في جافا سكريبت، وأهم الدوال (push وslice وmap وأخواتها) التي تقوم بمعظم العمل الثقيل.
الأسئلة الشائعة
كيف ننشئ كائنًا في JavaScript؟
الطريقة الأكثر شيوعًا هي استخدام الـ object literal، مثل: const user = { name: 'Ada', age: 30 }. الأقواس المعقوفة تحتوي على أزواج مفتاح-قيمة مفصولة بفواصل. المفاتيح عبارة عن نصوص (علامات الاقتباس اختيارية إذا كان الاسم معرّفًا صحيحًا)، والقيم يمكن أن تكون أي شيء: أرقام، نصوص، مصفوفات، دوال، أو حتى كائنات أخرى.
ما الفرق بين صيغة النقطة وصيغة الأقواس المربعة؟
user.name وuser['name'] يؤديان نفس الغرض عندما يكون المفتاح معروفًا ومكتوبًا مباشرة. استخدم الأقواس المربعة عندما يكون المفتاح مخزّنًا في متغير (user[key])، أو يحتوي على محارف لا تقبلها النقطة (كالمسافات أو الشرطة -)، أو يبدأ برقم. في بقية الحالات، صيغة النقطة أنظف وأكثر قراءة.
كيف نتحقق من وجود خاصية معينة في كائن؟
استخدم 'name' in user للتحقق من وجود المفتاح، بما في ذلك الخصائص الموروثة. أما Object.hasOwn(user, 'name') فيتحقق فقط من الخصائص الخاصة بالكائن نفسه، وهو البديل الحديث لـ hasOwnProperty. يمكنك أيضًا كتابة user.name !== undefined، لكنه يعطي نتيجة مضلّلة إذا كانت قيمة الخاصية مضبوطة صراحةً على undefined.
كيف نمرّ على محتويات كائن في JavaScript؟
الحلقة for (const key in obj) تمرّ على المفاتيح بما فيها الموروثة. لكن الأشيع عمليًا هو استخدام Object.keys(obj) أو Object.values(obj) أو Object.entries(obj) مع for...of أو .forEach(). ميزة Object.entries أنها تعطيك المفتاح والقيمة معًا في كل دورة، وهو ما تحتاجه غالبًا.