Menu

Циклы в TypeScript: for, forEach, for...of и for...in

Все способы написать цикл в TypeScript и типы, которые даёт каждый: классический for, for...of по массивам, Map и строкам, 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 и не требует аннотаций.

Классический цикл for

for (init; condition; update) подходит для счёта, шага больше единицы или прохода в обратном направлении. TypeScript выводит тип счётчика из начального значения.

Объявляйте счётчик через let, а не var. С let каждая итерация получает свою привязку, и колбэк, созданный в цикле, видит значение своей итерации.

for...of: значения массивов, строк и Map

for...of работает с любым итерируемым объектом, и переменная цикла получает соответствующий тип. Чтобы получить ещё и индекс, перебирайте array.entries(), который выдаёт кортежи [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 в условии переменная в теле сужена.

Метки, 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 (не цикл)
Считать или шагать по 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 внутри колбэка даёт ошибку компиляции 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 это объединение типов свойств. Если 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) => ...)).

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ