في TypeScript الحلقات نفسها الموجودة في JavaScript: for وfor...of وfor...in وwhile وdo...while، ودالة المصفوفات forEach. وما تضيفه TypeScript هو الأنواع: متغير الحلقة في for...of يأخذ نوع العنصر، ومفتاح for...in دائمًا string.
في معظم حلقات المصفوفات، for...of هو الخيار الأول: يُقرأ بوضوح، ويدعم break وcontinue وawait، ولا يحتاج إلى أي تصريح بالأنواع.
حلقة for الكلاسيكية
for (init; condition; update) هي الحلقة المناسبة للعدّ، أو للتقدم بخطوة أكبر من واحد، أو للمرور بترتيب عكسي. وتستنتج TypeScript نوع العدّاد من قيمته الأولى.
صرّح بالعدّاد بـ let لا بـ var. مع let يحصل كل تكرار على ربط خاص به، فترى دالة رد النداء المنشأة داخل الحلقة قيمة ذلك التكرار.
for...of: قيم المصفوفات والنصوص وMaps
يعمل for...of على أي شيء قابل للتكرار، ويأخذ متغير الحلقة النوع المناسب. وللحصول على الفهرس أيضًا، مرّ على array.entries() الذي ينتج tuples من الشكل [index, value].
يخرج break من الحلقة وينتقل continue إلى العنصر التالي، تمامًا كما في حلقة for. تفاصيل وقت التشغيل (الكائنات القابلة للتكرار والمولّدات) موجودة في صفحة JavaScript عن for...of وfor...in.
forEach: بلا break وبلا await
يستدعي forEach دالة لكل عنصر ويعيد undefined. وتأخذ معاملات دالة رد النداء أنواعها من المصفوفة، فلا حاجة إلى تصريح بالأنواع. وهناك أمران لا يستطيع فعلهما:
- التوقف مبكرًا.
breakداخل دالة رد النداء هو خطأ الترجمة TS1107،Jump target cannot cross function boundary.وreturnينهي الاستدعاء الحالي فقط، فيتصرف مثلcontinue. - انتظار العمل غير المتزامن. يتجاهل
forEachالوعد الذي تعيده دالة رد نداءasync.
من السهل ألا تنتبه لفخ العمل غير المتزامن، لأنه يُترجم دون أي تحذير:
تنتهي دوال رد النداء في forEach بترتيب المؤقتات (10 ثم 20 ثم 30) بعد أن تكون الحلقة قد تجاوزتها. أما نسخة for...of فتشغّلها بالتتابع. وللعمل المتوازي الذي تريد انتظاره مع ذلك، استخدم await Promise.all(items.map(async (item) => { ... })).
for...in: المفاتيح نصوص
يمر for...in على أسماء الخصائص القابلة للتعداد في كائن. ونوع المفتاح دائمًا string، حتى في المصفوفات حيث المفاتيح هي الفهارس كنصوص. وهذا يجعله غير مريح للوصول إلى كائن ذي نوع محدد:
index.ts(5,22): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.
تعطي TypeScript المفتاح النوع string عن قصد: وقت التشغيل قد يملك الكائن خصائص أكثر مما يذكره نوعه (التنميط البنيوي يسمح بخصائص إضافية)، ويمر for...in أيضًا على الخصائص الموروثة القابلة للتعداد. لذا لا تستطيع أن تعد بأن المفتاح هو "name" | "age". الحلول في القسم التالي. وتجنّب for...in على المصفوفات تمامًا.
المرور على مفاتيح الكائن وقيمه
يعطي Object.entries أزواج [key, value] وهو الطريقة المعتادة للمرور على كائن. ويعيد Object.keys النوع string[] للسبب نفسه الذي يجعل for...in كذلك، فعندما تعرف أن الكائن ليس فيه مفاتيح إضافية، أكّد نوع المفتاح.
للبيانات التي لا تُعرف مفاتيحها مسبقًا، أعطِ الكائن النوع Record<string, number> أو استخدم Map؛ وكلاهما مشروح في صفحة القاموس.
while وdo...while
يفحص while شرطه قبل كل دورة؛ ويشغّل do...while الجسم مرة واحدة قبل الفحص. وهما مناسبان للحلقات التي لا تمر على مجموعة، مثل القراءة حتى يتحقق شرط. ويعمل التضييق داخلهما: بعد فحص !== undefined في الشرط، يُضيَّق نوع المتغير داخل الجسم.
التسميات (labels) وbreak وcontinue في الحلقات المتداخلة
يخرج break العادي من الحلقة الداخلية فقط. للخروج من حلقة خارجية، ضع لها تسمية واذكر التسمية:
أي حلقة تستخدم
| الهدف | الحلقة |
|---|---|
| كل عنصر في مصفوفة | for (const x of arr) |
| العنصر والفهرس | for (const [i, x] of arr.entries()) أو arr.forEach((x, i) => ...) |
| التوقف مبكرًا | for...of مع break، أو find وsome وevery |
| انتظار كل خطوة | for...of مع await بداخلها |
| بناء مصفوفة جديدة | map وfilter (ليست حلقة) |
| العدّ أو التقدم بخطوة n | for (let i = 0; ...; i += n) |
| مفاتيح الكائن وقيمه | for (const [k, v] of Object.entries(obj)) |
| مُدخلات Map | for (const [k, v] of map) |
| حتى يتحقق شرط | while |
الأسئلة الشائعة
كيف أكتب حلقة for في TypeScript؟
كما في JavaScript: for (let i = 0; i < 5; i++) { ... }؛ وتستنتج TypeScript أن i من النوع number. وللمرور على عناصر مصفوفة، for (const item of items) أقصر، ويأخذ item نوع عناصر المصفوفة.
كيف أخرج من forEach في TypeScript؟
لا يمكنك: break داخل دالة رد النداء هو خطأ الترجمة TS1107 (Jump target cannot cross function boundary)، وreturn ينهي استدعاء رد النداء الحالي فقط، مثل continue. استخدم for...of مع break، أو some/find/every، التي تتوقف مبكرًا بطبيعتها.
ما الفرق بين for...of وfor...in في TypeScript؟
يمر for...of على قيم كائن قابل للتكرار (عناصر المصفوفة، وأحرف النص، ومُدخلات Map) بأنواعها الحقيقية. ويمر for...in على مفاتيح الخصائص القابلة للتعداد في كائن، ونوعها دائمًا string، بما فيها فهارس المصفوفة كنصوص. استخدم for...of للمصفوفات وObject.entries للكائنات.
كيف أمر على مفاتيح كائن وقيمه في TypeScript؟
استخدم for (const [key, value] of Object.entries(obj)). يكون key من النوع string وvalue هو union أنواع الخصائص. وإذا احتجت إلى أن يكون key من النوع keyof typeof obj، فأكّد النوع: (Object.keys(obj) as (keyof typeof obj)[])، لأن TypeScript تعطي Object.keys النوع string[] عن قصد.
هل يمكن استخدام await داخل forEach؟
يُترجم الكود، لكن forEach لا ينتظر الوعود التي تعيدها دالة رد النداء، فتنتهي الحلقة قبل انتهاء العمل. استخدم for...of مع await بداخلها للعمل المتتابع، أو await Promise.all(items.map(async (x) => ...)) لتشغيله بالتوازي.