Menu

לולאות ב-TypeScript: for, forEach, for...of ו-for...in

כל הדרכים לכתוב לולאה ב-TypeScript והטיפוסים שכל אחת נותנת: לולאת for הקלאסית, for...of על מערכים, maps ומחרוזות, for...in ומפתחות המחרוזת שלו, forEach (בלי break ובלי await), לולאות מוטפסות על מפתחות של אובייקט, ו-while.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

ל-TypeScript יש אותן לולאות כמו ל-JavaScript: for, for...of, for...in, while, do...while, ומתודת המערך forEach. מה ש-TypeScript מוסיף הוא הטיפוסים: למשתנה הלולאה של for...of יש את טיפוס האיבר, ומפתח של for...in הוא תמיד string.

ברוב הלולאות על מערכים, for...of הוא הבחירה הטבעית: הוא קריא, תומך ב-break, ב-continue וב-await, ולא צריך annotations.

לולאת for הקלאסית

for (init; condition; update) היא הלולאה לספירה, לקפיצות של יותר מאחד, או למעבר מהסוף להתחלה. TypeScript מסיק את טיפוס המונה מהערך ההתחלתי שלו.

הצהירו על המונה עם let, לא עם var. עם let, כל איטרציה מקבלת binding משלה, ולכן callback שנוצר בלולאה רואה את הערך של האיטרציה שלו.

for...of: ערכים של מערכים, מחרוזות ו-Maps

for...of עובד על כל דבר שהוא iterable, ומשתנה הלולאה מקבל את הטיפוס המתאים. כדי לקבל גם את האינדקס, עברו בלולאה על array.entries(), שמחזיר tuples של [index, value].

break יוצא מהלולאה ו-continue מדלג לפריט הבא, בדיוק כמו בלולאת for. הפרטים של זמן הריצה (iterables, generators) נמצאים בעמוד של JavaScript על for...of ו-for...in.

forEach: בלי break, בלי await

forEach קורא לפונקציה עבור כל איבר ומחזיר undefined. הטיפוסים של הפרמטרים של ה-callback נלקחים מהמערך, כך שלא צריך annotations. יש שני דברים שהוא לא יכול לעשות:

  • לעצור מוקדם. break בתוך ה-callback הוא שגיאת הקומפילציה TS1107, Jump target cannot cross function boundary. return רק מסיים את הקריאה הנוכחית, ולכן הוא מתנהג כמו continue.
  • לחכות לעבודה אסינכרונית. forEach מתעלם מה-promise שמחזיר callback שהוא async.

קל לפספס את המלכודת האסינכרונית, כי היא מתקמפלת בלי שום אזהרה:

ה-callbacks של forEach מסתיימים לפי סדר הטיימרים (10, 20, 30), אחרי שהלולאה כבר המשיכה הלאה. הגרסה עם for...of מריצה אותם בזה אחר זה. לעבודה מקבילית שבכל זאת מחכים לה, השתמשו ב-await Promise.all(items.map(async (item) => { ... })).

for...in: המפתחות הם מחרוזות

for...in עובר בלולאה על שמות המאפיינים ה-enumerable של אובייקט. הטיפוס של המפתח הוא תמיד 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 בכוונה: בזמן ריצה לאובייקט יכולים להיות יותר מאפיינים ממה שהטיפוס שלו מפרט (structural typing מאפשר מאפיינים נוספים), ו-for...in עובר גם על מאפיינים enumerable שעברו בירושה. לכן הוא לא יכול להבטיח שהמפתח הוא "name" | "age". התיקונים נמצאים בחלק הבא. הימנעו לגמרי מ-for...in על מערכים.

מעבר בלולאה על המפתחות והערכים של אובייקט

Object.entries נותן זוגות של [key, value] והוא הדרך המקובלת לעבור בלולאה על אובייקט. Object.keys מחזיר string[] מאותה סיבה ש-for...in עושה זאת, ולכן כשאתם יודעים שלאובייקט אין מפתחות נוספים, עשו assertion לטיפוס המפתח.

לנתונים שהמפתחות שלהם לא ידועים מראש, תנו לאובייקט את הטיפוס Record<string, number> או השתמשו ב-Map; שניהם מוסברים בעמוד על dictionary.

while ו-do...while

while בודק את התנאי שלו לפני כל סבב; do...while מריץ את הגוף פעם אחת לפני הבדיקה. הם מתאימים ללולאות שלא עוברות על אוסף, כמו קריאה עד שתנאי מתקיים. צמצום טיפוסים עובד בתוכן: אחרי בדיקת !== undefined בתנאי, המשתנה מצומצם בגוף הלולאה.

Labels, break ו-continue בלולאות מקוננות

break רגיל יוצא רק מהלולאה הפנימית ביותר. כדי לצאת מלולאה חיצונית, תנו לה label וציינו את שם ה-label:

באיזו לולאה להשתמש

מטרהלולאה
כל איבר במערך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 (לא לולאה)
לספור או לקפוץ ב-nfor (let i = 0; ...; i += n)
מפתחות וערכים של אובייקטfor (const [k, v] of Object.entries(obj))
רשומות של Mapfor (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 בתוך ה-callback הוא שגיאת הקומפילציה TS1107 (Jump target cannot cross function boundary), ו-return רק מסיים את הקריאה הנוכחית ל-callback, כמו continue. השתמשו ב-for...of עם break, או ב-some/find/every, שעוצרים מוקדם מעצם הגדרתם.

מה ההבדל בין for...of ל-for...in ב-TypeScript?

for...of עובר על הערכים של iterable (איברי מערך, תווים במחרוזת, רשומות ב-Map) עם הטיפוסים האמיתיים שלהם. for...in עובר על מפתחות המאפיינים ה-enumerable של אובייקט, שהטיפוס שלהם תמיד string, כולל אינדקסים של מערך כמחרוזות. השתמשו ב-for...of למערכים וב-Object.entries לאובייקטים.

איך עוברים בלולאה על המפתחות והערכים של אובייקט ב-TypeScript?

השתמשו ב-for (const [key, value] of Object.entries(obj)). key הוא string ו-value הוא ה-union של טיפוסי המאפיינים. אם אתם צריכים ש-key יהיה מטיפוס keyof typeof obj, עשו assertion: (Object.keys(obj) as (keyof typeof obj)[]), כי TypeScript נותן ל-Object.keys את הטיפוס string[] בכוונה.

אפשר להשתמש ב-await בתוך forEach?

זה מתקמפל, אבל forEach לא מחכה ל-promises שה-callback מחזיר, ולכן הלולאה מסתיימת לפני שהעבודה מסתיימת. השתמשו ב-for...of עם await בפנים לעבודה בזה אחר זה, או ב-await Promise.all(items.map(async (x) => ...)) כדי להריץ אותה במקביל.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל