July 21, 2022

Learn JavaScript #8. Циклы while и for.

При написании скриптов зачастую встаёт задача сделать однотипное действие много раз.

Например, вывести товары из списка один за другим, либо просто перебрать все числа от 1 до 10 и для каждого выполнить одинаковый код.

Для многократного повторения одного участка кода предусмотрены циклы.

Цикл "While"

Цикл while имеет следующий синтаксис:

while (condition) {
  // код
  // также называймый "телом цикла"
}

Код из тела выполняется, пока условие condition истинно.

Например, цикл ниже выводит i, пока i < 3:

let i = 0;
while (i < 3) { // выводит 0, затем 1, затем 2
  alert( i );
  i++;
}

Одно выполнения тела цикла по-научному называется итерация. Цикл в примере выше совершает три итерации.

Если бы строка i+= отсутствовала в примере выше, то цикл бы повторялся (в теории) вечно. На практике, конечно, браузер не позволит такому случиться, он предоставит пользователю возможность остановить "подвисший" скрипт, а JavaScript на стороне сервера придётся "убить" процесс.

Цикл "for"

Более сложный, но при этом самый распространённый цикл - цикл for.

Выглядит он так:

for (начало; условие; шаг) {
  // ... тело цикла ...
}

Давайте разберёмся, что означает каждая часть, на примере. Цикл ниже выполняет alert(i) для i от 0 до (но не включая) 3:

let i = 0

for (i = 0; i < 3; i++) { // выведет 0, затем 1, затем 2
  alert(i);
}

Рассмотрим конструкцию for подробней:

  • Начало - let i = 0 - Выполняется один раз при входе в цикл
  • Условие - i < 3 - Проверяется перед каждой итерацией цикла, если оно вычислится в false, цикл остановится.
  • Тело - alert(i) - Выполняется снова и снова, пока условие вычисляется в true.
  • Шаг - i++ - Выполняется после тела цикла на каждой итерации перед проверкой условий.

Прерывание цикла: "break"

Обычно цикл завершается при вычислении условия в false.

Но мы можем выйти из цикла в любой момент с помощью специальной директивы break.

Например, следующий код подсчитывает сумму вводимых чисел до тех пор, пока посетитель их вводит, а затем - выдаёт:

let sum = 0;

while (true) {

   let value = +prompt('Введите число', '');

   if (!value) break; // (*)

   sum += value;

}

 alert( 'Сумма: ' + sum );

Директива break в строке (*) полностью прекращает выполнение цикла и передаёт управление на строку за его телом, то есть на alert.

Вообще, сочетание "бесконечный цикл + break" - отличная штука для тех ситуаций, когда условие, по которому нужно прерваться, находится не в начале или конце цикла, а посередине или даже в нескольких местах его тела.

Переход к следующей итерации: countinue

Директива continue - "облегчённая версия" break. При её выполнении цикл не прерывается, а переходит к следующей итерации (если условие всё ещё равно true).

Её используют, если понятно, что на текущем повторе цикла делать больше нечего.

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

let i = 0;

for (i = 0, i < 10; i++) {

  // если true, то пропустит оставшуюся часть тела в цикле
  if (i % 2 == 0) continue;

  alert(i); // 1, затем 3, 5, 7, 9
}

Для чётных значений i, директива continue прекращает выполнение тела цикла и передаёт управление на следующую итерацию for (со следующим числом). Таким образом alert вызывается только для нечётных значений.

Метки для break/continue

Бывает, нужно выйти одновременно из нескольких уровней цикла сразу.

Например, в коде ниже мы проходимся циклами по i и j, запрашивая с помощью prompt координаты (i, j) с (0,0) до (2,2):

let i = 0;
let j = 0;

  for (i; i < 3; i++) {

    for (j; j < 3; J++) {

      let input = prompt('Значение на координатах (${i},${j})', '');

      // Что если мы захотим перейти к Готово (ниже) прямо отсюда?
      }
     }

alert('Готово');

${i},${j} - Вы видите это внутри строки, разделенной символом ``?. Например, «'Меня зовут #39; {имя}». Это новые литеральные шаблоны JS. JS заменит ${token} значением переменной. Это гораздо более приятный синтаксис, чем "Меня зовут" + имя.

Нам нужен способ остановить выполнение если пользователь отменит ввод.

Обычный break после input лишь прервёт внутренний цикл, но этого недостаточно. Достичь желаемого поведения можно с помощью меток.

Метка имеет вид идентификатора с двоеточием перед циклом:

labelName: for (...) {
   ...
}

Вызов break <labelName> в цикле ниже ищет ближайший внешний цикл с такой меткой и переходит в его конец.

let i = 0
let j = 0

outer: for (i; i < 3; i++) {

   for (j; j < 3; j++) {
 
    let input = prompt('Значение на координатах (${i},${j})', '');

    // если пустая строка или Отмена, то выйти из обоих циклов
    if (!input) break outer; // (*)
    
    // сделать что-нибудь со значениями...
   }
  }

alert('Готово!');

В примере выше это означает, что вызовом break outer будет разорван внешний цикл до метки с именем outer.

Таким образом управление перейдёт со строки, помеченной (*), к alert('Готово!').

Также можно размещать метку на отдельной строке:

outer:
for (i; i < 3; i++) { ... }

Директива continue также может быть использована с меткой. В этом случае управление перейдёт на следующую итерацию цикла с меткой.

Важно

Метки не позволяют "прыгнуть" куда угодно

Метки не дают возможности передавать управление в произвольное место кода.

Например, нет возможности сделать следующее:

break label; // не прыгнет к метке ниже

label: for (...)

Директива break должна находиться внутри блока кода. Технически, подойдёт любой маркированный блок кода, например:

label: {
  // ...
  break label; // работает
  // ...
}

... Хотя в 99.9% случаев break используется внутри циклов, как мы видели в примерах выше.

К слову, continue возможно только внутри цикла.

Итого

Мы рассмотрели 3 вида циклов:

  • while - Проверяет условие перед каждой итерацией
  • do...while - Проверяет условие после каждой итерации
  • for (;;) - Проверяет условие перед каждой итерацией, есть возможность задать дополнительные настройки

Чтобы организовать бесконечный цикл, используют конструкцию while (true). При этом он, как и любой другой цикл, может быть прерван директивой break.

Если на данной итерации цикла делать больше ничего не надо, но полностью прекращать цикл не следует - используйте директиву continue.

Обе этих директивы поддерживают метки, которые ставятся перед циклом. Метки - единственный способ для break/continue выйти за пределы текущего цикла, повлиять на выполнение внешнего.

Заметим, что метки не позволяют прыгнуть в произвольное место кода, в JS нет такой возможности.

Задачи

Последнее значение цикла

Какое последнее значение выведет этот код? Почему?

let i = 3;

while (i) {
  alert ( i-- );
}

// Последнее значение будет 1, так как while (i) это while (i = 0)

Какие значения выведет цикл while?

Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом.

  • Префиксный вариант ++i:
let i = 0;
while (++i < 5) alert( i );

// 1, 2, 3, 4, потому что ++i возвращает значение после увеличение
  • Постфиксный вариант i++:
let i = 0;
while (i++ < 5) alert( i );

// 0, 1, 2, 3, 4, потому что i++ возвращает значение, а затем увеличивает
// Неправильный ответ 

Ответ оказался неправильным во втором варианте, потому что первым значением будет i = 1 из-за того, что в сравнении будет участвовать старое значение i = 0, а выведено будет уже увеличенное, то есть 1.

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

При i = 4 произойдёт сравнение while (4 < 5) и оно окажется верным, поэтому после этого сработает i++, увеличив i до 5, так что значение 5 будет выведено. Оно станет последним.

Какие значения выведет цикл for?

Для каждого цикла запишите, какие значения он выведет.

  • Постфиксная форма:
for (let i = 0, i < 5; i++) alert( i );

Так как шаг i++ выполняется после тела цикла на каждой итерации перед проверкой условий, то в alert будут выведены значения: 0, 1, 2, 3, 4

  • Префиксная форма:
for (let i = 0; i < 5; ++i) alert( i );

Так как шаг ++i выполняется после тела цикла на каждой итерации перед проверкой условий, то в alert будут выведены значения: 0, 1, 2, 3, 4

Такой результат обусловлен алгоритмом работы for:

  1. Выполнить единожды присваивание i = 0 перед чем-либо (начало).
  2. Проверить условие i < 5
  3. Если true - выполнить тело цикла alert(i), и затем i++

Выведите чётные числа

При помощи цикла for выведите чётные числа от 2 до 10.

let i = 2;

for (i; i <= 10; i++) {
  if (i % 2 == 0) {
    alert(i);
  }
}

Замените for на while

Перепишите код, заменив цикл for на while, без изменения поведения цикла.

for (let i = 0; i < 3; i++) {
  alert('number ${i}!');
}

Решение:

let i = 0

while (i < 3) {
  alert('number ${i}!');
  i++;
}

Повторять цикл, пока ввод неверен

Напишите цикл, который предлагает prompt ввести число большее 100. Если посетитель ввёл другое число - попросить ввести ещё раз, и так далее.

Цикл должен спрашивать число пока либо посетитель не введёт число, большее 100, либо нажмёт кнопку "Отмена (ESC)".

Предполагается, что посетитель вводит только числа.

let number; 

do {
  number = prompt('Введите число больше 100', '0')
} while (number <= 100 && number);

Я знатно сгорел на этой простой задачке в 5 строк. Описывать все тяготы не буду, поэтому просто скажу, что не смог её сделать.

Вывести простые числа

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

Другими словами, n > 1 – простое, если при его делении на любое число кроме 1 и n есть остаток.

Например, 5 это простое число, оно не может быть разделено без остатка на 2, 3 и 4.

Напишите код, который выводит все простые числа из интервала от 2 до n.

Для n = 10 результат должен быть 2,3,5,7.

P.S. Код также должен легко модифицироваться для любых других интервалов.

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // Для всех i...

  for (let j = 2; j < i; j++) { // проверить, делится ли число..
    if (i % j == 0) continue nextPrime; // не подходит, берём следующее
  }

  alert( i ); // простое число
}

Аналогичная ситуация и с этой задачкой, решить я её не смог. Сначала минут 10 я пытался понять, что такое простые числа(мем), а затем пол часа пытался понять, как их можно проверить. Тильт, что тут сказать...


Telegram-канал: unknown.dev