حلقتان متشابهتان بالشكل، لكن كل واحدة لها مهمتها
في جافا سكريبت فيه صيغتان للحلقات شكلهما متطابق تقريبًا، لكن وظيفة كل واحدة مختلفة تمامًا. حلقة for...of تمرّ على القيم داخل أي كائن قابل للتكرار (iterable)، بينما for...in تمرّ على المفاتيح الخاصة بالكائن. الفرق بين for of و for in مجرد حرف واحد، لكن السلوك مختلف جذريًا.
وهذا المثال التالي يلخّص القصة كاملة:
الحلقة الأولى تطبع تفاح وموز وكرز — أي القيم. أما الثانية فتطبع 0 و1 و2 — أي المفاتيح على هيئة نصوص. اختَر الخطأ منهما وستقضي عشر دقائق تتساءل لماذا امتلأت مصفوفتك بالأرقام.
for...of: المرور على القيم في أي كائن قابل للتكرار
الحلقة for...of هي الخيار الذي ستلجأ إليه في معظم الأحيان. تعمل مع أي شيء تعتبره جافا سكريبت قابلاً للتكرار (iterable): المصفوفات، السلاسل النصية، Map، Set، NodeList، المولِّدات (generators)، وغيرها.
لا داعي للعبث بالفهارس، ولا حاجة لكتابة scores[i]. تطلب كل قيمة، فتحصل على كل قيمة مباشرة. حتى النصوص قابلة للتكرار في جافا سكريبت — حلقة for...of تمرّ عليها حرفًا حرفًا:
يعمل هذا بشكل صحيح مع معظم نقاط Unicode أيضًا، وهي ميزة صغيرة لكنها حقيقية مقارنةً بالوصول إلى أحرف النص عبر رقم الفهرس.
الحصول على index في for of
الشيء الوحيد الذي لا تمنحك إياه حلقة for...of مباشرةً هو موضع العنصر. عندما تحتاجه، اجمع الحلقة مع entries():
تُرجع names.entries() أزواجًا على شكل [index, value]، ثم يقوم التفكيك (destructuring) بتقسيمها إلى متغيّرَين. هذا الأسلوب عادةً أنظف من العودة إلى حلقة for (let i = 0; ...) التقليدية لمجرّد أنك بحاجة إلى i.
for...in في جافا سكريبت: المرور على مفاتيح الكائن
حلقة for...in مُصمَّمة للكائنات العادية، وتمرّ على المفاتيح النصية القابلة للتعداد (enumerable):
لاحظ أنك تحصل على المفتاح فقط، لا على القيمة. وللوصول إلى القيمة، تعود إلى الكائن عبر user[key]. كل مفتاح هنا سلسلة نصية — حتى لو بدا وكأنه رقم.
كما أن for...in يصعد في سلسلة البروتوتايب (prototype chain)، مما يعني أنه قد يكشف عن خصائص موروثة. في الكائنات الحرفية (literal objects) التي تنشئها بنفسك، نادرًا ما يُشكّل ذلك مشكلة، لكن عند المرور على نُسخ (instances) من صنف (class) أو كائنات قادمة من مكتبة خارجية، يُفضَّل أن تتعامل بحذر:
Object.hasOwn(user, key) يتخطى أي شيء موروث. في الكود الحديث، معظم المطورين يتجنبون for...in تمامًا، ويستخدمون بدلاً منه Object.keys أو Object.values أو Object.entries — وهذا يقودنا مباشرة للقسم التالي.
الطريقة الحديثة للمرور على كائن في جافا سكريبت
بدلًا من for...in، استخدم for...of مع إحدى دوال Object.* المساعدة. اختر منها ما يناسب حالتك:
Object.entries مريحة جدًا بشكل خاص — تفكيك الزوج [key, value] يجعل الكود يُقرأ وكأنه جملة عادية. وبما أن هذه التوابع تُرجع فقط الخصائص القابلة للتعداد المملوكة للكائن نفسه، فلن تقلق من الخصائص الموروثة غير المرغوبة.
تجنّب استخدام for...in مع المصفوفات
تقنيًا، الأمر مسموح، لكنه يوقع المطورين في أخطاء متكررة:
ستحصل على 0 و1 و2، وأيضًا tag. أي خاصية أضافها أحدهم إلى المصفوفة — أو إلى Array.prototype عبر polyfill — ستظهر هي الأخرى. كما أن المفاتيح هنا عبارة عن نصوص (strings)، لذلك فإن key + 1 يقوم بدمج النصوص بدلًا من الجمع الحسابي. زِد على ذلك أن ترتيب المرور لا يضمن لك مطابقة ترتيب عناصر المصفوفة في كل الحالات.
قاعدة عامة تختصر لك الموضوع:
- تريد قيم المصفوفة فقط؟ استخدم
for...of arr. - تريد الـ index والقيمة معًا؟ استخدم
for...of arr.entries(). - تريد الـ index فقط مع عدّ يدوي؟ الحلقة الكلاسيكية
for (let i = 0; i < arr.length; i++). - تريد المرور على مفاتيح أو خصائص كائن؟ استخدم
Object.keys(obj)أوObject.entries(obj)معfor...of.
باختصار: for...in أداة لاستخدامات ضيقة جدًا، بينما for...of مع دوال Object المساعدة يغطّي لك كل ما تحتاجه عمليًا.
break وcontinue يعملان في الحلقتين
كلتا الحلقتين تدعمان أدوات الخروج المبكر المعتادة:
العبارة continue تتخطى الدورة الحالية وتنتقل للي بعدها، أما break فتخرج من الحلقة كلياً. وهذا بالضبط السبب الرئيسي اللي يخلّي for...of أفضل من .forEach() — لأن دالة الـ callback في forEach ما تقدر تستخدم break للخروج من الحلقة، بينما for...of تقدر.
مقارنة سريعة جنباً إلى جنب
أربع حلقات، أربع مهام، وفكرة واحدة تحكمها كلها: إذا كنت تريد القيم من شيء قابل للتكرار (iterable)، استخدم for...of. أما إذا أردت التعامل مع خصائص كائن، فمرّ عليها عبر Object.keys أو Object.values أو Object.entries، ومع for...of أيضًا. واترك for...in للحالة النادرة التي تحتاج فيها فعلًا إلى كل المفاتيح النصية القابلة للعدّ في كائن ما، سواء كانت موروثة أم لا.
التالي: القيم الصادقة والكاذبة (Truthy و Falsy)
كل حلقة وكل جملة if في جافا سكريبت تطرح في النهاية السؤال ذاته: هل تُعتبر هذه القيمة صحيحة؟ والإجابة ليست دائمًا بديهية — فالسلسلة الفارغة، والصفر، وnull، وundefined تتصرف كلها بشكل مختلف عمّا قد تتوقعه. القيم الصادقة والكاذبة هي موضوعنا القادم.
الأسئلة الشائعة
ما الفرق بين for...of و for...in في JavaScript؟
حلقة for...of تمر على قيم أي عنصر قابل للتكرار مثل المصفوفات والنصوص و Map و Set، بينما for...in تمر على مفاتيح (أسماء خصائص) الكائن. مثلاً في المصفوفة ['a', 'b']، تعطيك for...of القيمتين 'a' و 'b'، أما for...in فتعطيك '0' و '1' كسلاسل نصية.
هل يمكنني استخدام for...of مع الكائنات (Objects)؟
ليس بشكل مباشر، لأن الكائنات العادية ليست قابلة للتكرار. الحل هو استخدام Object.keys(obj) أو Object.values(obj) أو Object.entries(obj) للحصول على مصفوفة يمكنك المرور عليها بـ for...of. الصيغة الأكثر شيوعاً هي: for (const [key, value] of Object.entries(obj)).
لماذا يُنصح بعدم استخدام for...in مع المصفوفات؟
تقنياً تعمل، لكن for...in تمر على كل الخصائص القابلة للعدّ، بما فيها الخصائص الموروثة وأي إضافات على Array.prototype. كما أنها تُرجع المفاتيح كسلاسل نصية، والترتيب ليس مضموناً دائماً مع المفاتيح الرقمية. استخدم for...of إذا أردت القيم، أو حلقة for التقليدية إذا كنت بحاجة إلى الفهرس (index).