Главная / Статьи

Основы циклов в TypeScript: while, do-while, for, break и continue

Узнайте, как циклы while, do-while и for в TypeScript оценивают свои условия, почему изменение области видимости переменной цикла влияет на результаты, и как операторы break и continue изменяют ход выполнения кода.

Издатель: REACTAPP.TOP9 мин чтения1983 слов

Источники: Mastering Loops in TypeScript: while, do-while, for, break & continue

Циклы позволяют выполнять один и тот же блок кода неоднократно до тех пор, пока выполняется определённое условие, вместо того чтобы постоянно копировать одно и то же предложение. Они кажутся простыми, но большинство ошибок в циклах возникают из-за трёх аспектов: момента проверки условия, местоположения переменной цикла и расположения команд break или continue внутри тела цикла. В этом руководстве рассматриваются циклы while, do-while и for в TypeScript с простыми примерами, результаты выполнения которых можно предсказать по шагам, чтобы вы могли читать и писать код циклических операций без неожиданностей.

Почему циклический подход лучше повторения

Чтобы вывести числа от 1 до 10 без использования цикла, потребуется десять вызовов console.log. С помощью цикла вы описываете работу один раз, а среда выполнения повторяет её; размер кода остаётся прежним независимо от того, нужно ли десять или десять тысяч повторений.

Цикл while: сначала проверка, затем выполнение

Цикл while имеет самую простую структуру из всех:

while (condition) {
  // statements
}

Условие оценивается до каждой итерации. Пока оно равно true, выполняется тело цикла; как только оно становится false, цикл завершается. Чтобы цикл while корректно работал, необходимы три элемента:

  • начальное значение переменной, от которой зависит условие;
  • само условие;
  • что-то в теле цикла, что изменяет значение этой переменной так, чтобы условие стало false.

Если пропустить третий элемент, цикл никогда не остановится — об этой проблеме речь пойдет ниже.

Счисление от 1 до 10

Здесь видны все три элемента: переменная i начинает с значения 1, цикл выполняется до тех пор, пока i <= 10, а операция i++ увеличивает ее значение после каждой итерации:

let i: number = 1; // initialization
while (i <= 10) {
  console.log(i);
  i++;
}

Фильтрация четных чисел

Чтобы вывести только четные числа в этом диапазоне, продолжайте перебирать каждое значение, но добавьте проверку внутри цикла. Оператор остатка от деления % возвращает остаток после деления, поэтому i % 2 == 0 истинно тогда, когда i — четное число:

let i: number = 1;
while (i <= 10) {
  if (i % 2 == 0) {
    console.log(i);
  }
  i++;
}

Существует более эффективный способ достижения похожего результата: полностью пропустите проверку и с каждым разом увеличивайте значение на два. Обратите внимание, что в этой версии начало счисления — 0, поэтому в результате также будет содержаться 0:

let i: number = 0;
while (i <= 10) {
  console.log(i);
  i = i + 2;
}

Результат: 0, 2, 4, 6, 8, 10

Фильтрация нечетных чисел

Если изменить условие на != 0, будут выбираться нечетные числа. Начинать с 0 здесь безопасно, поскольку 0 является четным числом и будет отфильтровано:

let i: number = 0;
while (i <= 10) {
  if (i % 2 != 0) {
    console.log(i);
  }
  i++;
}

Результат: 1, 3, 5, 7, 9

В этих примерах используются операторы == и !=; обычно предпочитаются строгие формы === и !==, поскольку они никогда не принудительно не преобразуют типы, хотя для двух чисел результат будет одинаковым.

Отсчёт назад

Чтобы отсчитывать назад, начните с большого значения и уменьшайте его, пока значение будет равно по меньшей мере 1:

let i: number = 10;
while (i >= 1) {
  console.log(i);
  i--;
}

Бесконечные циклы и причины их возникновения

Условие цикла while — это просто выражение, возвращающее логическое значение, поэтому while (true) является совершенно корректным. Приведённый ниже цикл будет выводить данные бесконечно, поскольку его невозможно прервать:

let i: number = 1;
while (true) {
  console.log(i);
  i++;
  // no break, no exit condition this runs forever
}

Чаще всего бесконечные циклы возникают случайно: переменная управления никогда не обновляется или меняет значения в неправильном направлении. Неконтролируемый цикл блокирует всё остальное в своей нити, из-за чего вкладка браузера или процесс Node.js кажутся зависшими. Намеренное использование конструкции while (true) допустимо, если предусмотрен явный способ выхода, обычно с помощью ключевого слова break (описано ниже), например, в логике «повторять попытки до успеха».

Цикл do-while: выполнение один раз, затем проверка

В цикле do-while условие проверяется в конце:

do {
  // statements
} while (condition);

Поскольку проверка происходит после выполнения тела цикла, тело всегда выполняется хотя бы один раз, независимо от значения условия. В отличие от этого, обычный цикл while может не выполняться вовсе.

Когда условие изначально истинно, цикл do-while ведет себя точно так же, как и цикл while:

let i: number = 1;
do {
  console.log(i);
  i++;
} while (i <= 5);

Разница проявляется, когда условие с самого начала является ложным:

let i: number = 10;
do {
  console.log(i);
  i++;
} while (i <= 5);

Результат: 10

Тело цикла выполняется безусловно, выводится 10, а переменная i увеличивается до 11. Только затем оценивается условие i <= 5; оно имеет значение false, поэтому цикл завершается после одной итерации. Цикл while (i <= 5), начинающийся с значения 10, ничего не выведет. Используйте do-while, когда первая итерация необходима в любом случае, например, для отображения запроса хотя бы один раз.

Цикл for: всё в одном заголовке

Цикл for — это тот, который вы будете использовать чаще всего, особенно когда заранее известно, сколько раз нужно повторить операции или насколько должен изменяться счётчик. Он объединяет инициализацию, условие и обновление в один заголовок, что делает его более компактным по сравнению с аналогичным циклом while:

for (initialization; condition; increment/decrement) {
  // statements
}

Следует запомнить порядок выполнения этих этапов:

  1. Инициализация происходит один раз, до любых других действий (например let i = 1).
  2. Оценивается условие. Если оно равно true, выполняется тело цикла; если false, цикл завершается.
  3. После тела цикла выполняется операция обновления (например i++).
  4. Управление возвращается к шагу 2, и цикл повторяется до тех пор, пока условие не станет false.

Другими словами, инициализация происходит один раз, в то время как проверка условия и обновление данных выполняются при каждом цикле.

Счёт от 1 до 10

for (let i = 1; i <= 10; i++) {
  console.log(i);
}

Два способа получения чётных чисел

Как и в случае с while, можно делать шаг по два:

for (let i = 2; i <= 10; i += 2) {
  console.log(i);
}

или обрабатывать каждое значение и фильтровать их внутри тела цикла:

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    console.log(i);
  }
}

Оба способа выводят одну и ту же последовательность:

2
4
6
8
10

Метод шагового перебора выполняет меньше операций, поскольку обрабатывает только чётные значения; фильтрация более гибка, когда правило не является фиксированным шагом, например «делится на 3 или 7».

Обратный отсчёт

Обновление путём уменьшения значения заставляет цикл выполняться в обратном порядке:

for (let i = 10; i >= 1; i--) {
  console.log(i);
}

Результат: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1

Почему переменная цикла равна 6 после завершения цикла

Место объявления переменной цикла влияет на её значение после завершения цикла. В этом примере i объявляется до начала цикла, а блок for лишь присваивает ей значение:

let i: number;
for (i = 1; i <= 5; i++) {
  console.log(i);
}
console.log(i);

Результат: 1, 2, 3, 4, 5, 6

Поскольку i находится во внешнем области видимости, последняя команда console.log(i) всё равно может к ней обратиться, сохраняя значение, оставленное циклом. Это число 6, а не 5, из-за порядка выполнения операций: в последнем проходе i равно 5, тело цикла выводит это значение, затем происходит его увеличение до 6, и только тогда условие 6 <= 5 становится ложным, что приводит к завершению цикла.

Более распространённый стиль — for (let i = 1; i <= 5; i++) — предоставляет i в блоковой области видимости, ограниченной циклом, поэтому после его завершения оно больше не существует. Обращение к нему после цикла вызывает ошибку компиляции в TypeScript («Cannot find name 'i'»), а в обычном JavaScript приводит к возникновению ошибки ReferenceError во время выполнения. Обычно лучше объявлять переменную в начале кода; использовать внешнее объявление следует только тогда, когда необходимо получить окончательное значение.

Изменение хода выполнения с помощью break и continue

Два оператора прерывания позволяют изменить обычный ход цикла прямо внутри его тела:

  • break немедленно завершает цикл, независимо от того, сколько итераций ещё предстоит выполнить.
  • continue прерывает только текущую итерацию и переходит к следующей; цикл продолжает работать.

В цикле for оператор continue всё равно выполняет выражение обновления перед повторной проверкой условия, поэтому счётчик продолжает увеличиваться.

break: важна позиция внутри тела цикла

Здесь проверка break происходит до вызова функции вывода:

let i: number;
for (i = 1; i <= 10; i++) {
  if (i == 5) {
    break;
  }
  console.log(i);
}

Результат: 1, 2, 3, 4

Когда значение i достигает 5, цикл завершается до того, как успевает выполниться console.log, поэтому число 5 не отображается.

Если переместить операцию вывода выше проверки, результат изменится:

let i: number;
for (i = 1; i <= 10; i++) {
  console.log(i);
  if (i == 5) {
    break;
  }
}

Результат: 1, 2, 3, 4, 5

Условие осталось прежним, но теперь отображается число 5, потому что операция вывода выполняется до проверки условия. Как и в случае с break, так и с continue, порядок выполнения операций внутри цикла определяет результат.

continue: пропуск одного значения

Замена break на continue превращает действие «остановиться на 5» в действие «пропустить 5»:

let i: number;
for (i = 1; i <= 10; i++) {
  if (i == 5) {
    continue;
  }
  console.log(i);
}

Результат: 1, 2, 3, 4, 6, 7, 8, 9, 10

Цикл продолжает работу; просто для этой итерации не выполняется операция вывода.

Пропуск всех нечётных чисел

В сочетании с фильтром continue направляет каждое нечётное значение сразу к следующей итерации:

let i: number;
for (i = 0; i <= 10; i++) {
  if (i % 2 != 0) {
    continue;
  }
  console.log(i);
}

Результат: 0, 2, 4, 6, 8, 10

Пропуск списка определённых значений

Условие может быть настолько сложным, насколько это необходимо. Используя логический оператор OR ||, этот цикл пропускает значения 3, 5 и 7:

let i: number;
for (i = 0; i <= 10; i++) {
  if (i == 3 || i == 5 || i == 7) {
    continue;
  }
  console.log(i);
}

Результат: 0, 1, 2, 4, 6, 8, 9, 10

Для более длинного списка значений для пропуска лучше использовать [3, 5, 7].includes(i) или структуру Set.

Основные выводы

  • В цикле while условие проверяется перед каждым проходом, и он может не выполниться ни разу; в цикле do-while проверка происходит после выполнения кода, поэтому он всегда выполняется хотя бы один раз.
  • Каждый цикл требует обновления, которое в конечном итоге сделает условие ложным; в противном случае возникнет бесконечный цикл, блокирующий поток.
  • В цикле for инициализация выполняется один раз, в то время как проверка условия и обновление происходят при каждом цикле, поэтому счётчик в итоге превышает установленный лимит на один шаг.
  • Объявление счётчика с помощью let в начале цикла ограничивает его действие только этим циклом; объявление вне цикла позволяет его окончательному значению сохраняться.
  • break прерывает цикл, а continue пропускает одну итерацию; место их размещения относительно других операторов влияет на результат выполнения.
  • Условные операторы определяют, какой код будет выполнен; циклы определяют частоту его выполнения. Вместе они покрывают большую часть схем управления, которые вы будете использовать.