Циклы while, for

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

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

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

Цикл while

Цикл while имеет вид:

while (условие) {
  // код, тело цикла
}

Пока условие верно – выполняется код из тела цикла.

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

var i = 0;
while (i < 3) {
  alert( i );
  i++;
}

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

Если бы i++ в коде выше не было, то цикл выполнялся бы (в теории) вечно. На практике, браузер выведет сообщение о «зависшем» скрипте и посетитель его остановит.

Бесконечный цикл можно сделать и проще:

while (true) {
  // ...
}

Условие в скобках интерпретируется как логическое значение, поэтому вместо while (i!=0) обычно пишут while (i):

var i = 3;
while (i) { // при i, равном 0, значение в скобках будет false и цикл остановится
  alert( i );
  i--;
}

Цикл do…while

Проверку условия можно поставить под телом цикла, используя специальный синтаксис do..while:

do {
  // тело цикла
} while (условие);

Цикл, описанный, таким образом, сначала выполняет тело, а затем проверяет условие.

Например:

var i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

Синтаксис do..while редко используется, т.к. обычный while нагляднее – в нём не приходится искать глазами условие и ломать голову, почему оно проверяется именно в конце.

Цикл for

Чаще всего применяется цикл for. Выглядит он так:

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

Пример цикла, который выполняет alert(i) для i от 0 до 2 включительно (до 3):

var i;

for (i = 0; i < 3; i++) {
  alert( i );
}

Здесь:

  • Начало: i=0.
  • Условие: i<3.
  • Шаг: i++.
  • Тело: alert(i), т.е. код внутри фигурных скобок (они не обязательны, если только одна операция)

Цикл выполняется так:

  1. Начало: i=0 выполняется один-единственный раз, при заходе в цикл.
  2. Условие: i<3 проверяется перед каждой итерацией и при входе в цикл, если оно нарушено, то происходит выход.
  3. Тело: alert(i).
  4. Шаг: i++ выполняется после тела на каждой итерации, но перед проверкой условия.
  5. Идти на шаг 2.

Иными словами, поток выполнения: начало → (если условиетелошаг) → (если условиетелошаг) → … и так далее, пока верно условие.

На заметку:

В цикле также можно определить переменную:

for (var i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}

Эта переменная будет видна и за границами цикла, в частности, после окончания цикла i станет равно 3.

Пропуск частей for

Любая часть for может быть пропущена.

Например, можно убрать начало. Цикл в примере ниже полностью идентичен приведённому выше:

var i = 0;

for (; i < 3; i++) {
  alert( i ); // 0, 1, 2
}

Можно убрать и шаг:

var i = 0;

for (; i < 3;) {
  alert( i );
  // цикл превратился в аналог while (i<3)
}

А можно и вообще убрать всё, получив бесконечный цикл:

for (;;) {
  // будет выполняться вечно
}

При этом сами точки с запятой ; обязательно должны присутствовать, иначе будет ошибка синтаксиса.

for..in

Существует также специальная конструкция for..in для перебора свойств объекта.

Мы познакомимся с ней позже, когда будем говорить об объектах.

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

Выйти из цикла можно не только при проверке условия но и, вообще, в любой момент. Эту возможность обеспечивает директива break.

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

var sum = 0;

while (true) {

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

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

  sum += value;

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

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

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

Следующая итерация: continue

Директива continue прекращает выполнение текущей итерации цикла.

Она – в некотором роде «младшая сестра» директивы break: прерывает не весь цикл, а только текущее выполнение его тела, как будто оно закончилось.

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

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

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

  if (i % 2 == 0) continue;

  alert(i);
}

Для чётных i срабатывает continue, выполнение тела прекращается и управление передаётся на следующий проход for.

Директива continue позволяет обойтись без скобок

Цикл, который обрабатывает только нечётные значения, мог бы выглядеть так:

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

  if (i % 2) {
    alert( i );
  }

}

С технической точки зрения он полностью идентичен. Действительно, вместо continue можно просто завернуть действия в блок if. Однако, мы получили дополнительный уровень вложенности фигурных скобок. Если код внутри if более длинный, то это ухудшает читаемость, в отличие от варианта с continue.

Нельзя использовать break/continue справа от оператора „?“

Обычно мы можем заменить if на оператор вопросительный знак '?'.

То есть, запись:

if (условие) {
  a();
} else {
  b();
}

…Аналогична записи:

условие ? a() : b();

В обоих случаях в зависимости от условия выполняется либо a() либо b().

Но разница состоит в том, что оператор вопросительный знак '?', использованный во второй записи, возвращает значение.

Синтаксические конструкции, которые не возвращают значений, нельзя использовать в операторе '?'.

К таким относятся большинство конструкций и, в частности, break/continue.

Поэтому такой код приведёт к ошибке:

(i > 5) ? alert(i) : continue;

Впрочем, как уже говорилось ранее, оператор вопросительный знак '?' не стоит использовать таким образом. Это – всего лишь ещё одна причина, почему для проверки условия предпочтителен if.

Метки для break/continue

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

Например, внутри цикла по i находится цикл по j, и при выполнении некоторого условия мы бы хотели выйти из обоих циклов сразу:

outer: for (var i = 0; i < 3; i++) {

  for (var j = 0; j < 3; j++) {

    var input = prompt('Значение в координатах '+i+','+j, '');

    // если отмена ввода или пустая строка -
    // завершить оба цикла
    if (!input) break outer; // (*)

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

В коде выше для этого использована метка.

Метка имеет вид "имя:", имя должно быть уникальным. Она ставится перед циклом, вот так:

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

Можно также выносить её на отдельную строку:

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

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

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

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

Итого

JavaScript поддерживает три вида циклов:

  • while – проверка условия перед каждым выполнением.
  • do..while – проверка условия после каждого выполнения.
  • for – проверка условия перед каждым выполнением, а также дополнительные настройки.

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

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

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

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

Задачи

важность: 3

Ответ: 1.

var i = 3;

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

Каждое выполнение цикла уменьшает i. Проверка while(i) даст сигнал «стоп» при i = 0.

Соответственно, шаги цикла:

var i = 3
alert( i-- ); // выведет 3, затем уменьшит i до 2

alert(i--) // выведет 2, затем уменьшит i до 1

alert(i--) // выведет 1, затем уменьшит i до 0

// все, проверка while(i) не даст выполняться циклу дальше

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

var i = 3;

while (i) {
  alert( i-- );
}
важность: 4
  1. От 1 до 4

    var i = 0;
    while (++i < 5) alert( i );

    Первое значение: i=1, так как операция ++i сначала увеличит i, а потом уже произойдёт сравнение и выполнение alert.

    Далее 2,3,4.. Значения выводятся одно за другим. Для каждого значения сначала происходит увеличение, а потом – сравнение, так как ++ стоит перед переменной.

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

  2. От 1 до 5

    var i = 0;
    while (i++ < 5) alert( i );

    Первое значение: i=1. Остановимся на нём подробнее. Оператор i++ увеличивает i, возвращая старое значение, так что в сравнении i++ < 5 будет участвовать старое i=0.

    Но последующий вызов alert уже не относится к этому выражению, так что получит новый i=1.

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

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

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

  1. Префиксный вариант

    var i = 0;
    while (++i < 5) alert( i );
  2. Постфиксный вариант

    var i = 0;
    while (i++ < 5) alert( i );
важность: 4

Ответ: от 0 до 4 в обоих случаях.

for (var i = 0; i < 5; ++i) alert( i );

for (var i = 0; i < 5; i++) alert( i );

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

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

Увеличение i++ выполняется отдельно от проверки условия (2), значение i при этом не используется, поэтому нет никакой разницы между i++ и ++i.

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

  1. Постфиксная форма:

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

    for (var i = 0; i < 5; ++i) alert( i );
важность: 5
for (var i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

Чётность проверяется по остатку при делении на 2, используя оператор «деление с остатком» %: i % 2.

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

Запустить демо
важность: 5
var i = 0;
while (i < 3) {
  alert( "номер " + i + "!" );
  i++;
}

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

for (var i = 0; i < 3; i++) {
  alert( "номер " + i + "!" );
}
важность: 5
var num;

do {
  num = prompt("Введите число больше 100?", 0);
} while (num <= 100 && num != null);

Цикл do..while повторяется, пока верны две проверки:

  1. Проверка num <= 100 – то есть, введённое число всё еще меньше 100.
  2. Проверка num != null – значение null означает, что посетитель нажал «Отмена», в этом случае цикл тоже нужно прекратить.

Кстати, сравнение num <= 100 при вводе null даст true, так что вторая проверка необходима.

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

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

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

Запустить демо
важность: 3

Схема решения

Для всех i от 1 до 10 {
  проверить, делится ли число i на какое - либо из чисел до него
  если делится, то это i не подходит, берем следующее
  если не делится, то i - простое число
}

Решение

Решение с использованием метки:

nextPrime:
  for (var i = 2; i < 10; i++) {

    for (var j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

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

Конечно же, его можно оптимизировать с точки зрения производительности. Например, проверять все j не от 2 до i, а от 2 до квадратного корня из i. А для очень больших чисел – существуют более эффективные специализированные алгоритмы проверки простоты числа, например квадратичное решето и решето числового поля.

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

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

Создайте код, который выводит все простые числа из интервала от 2 до 10. Результат должен быть: 2,3,5,7.

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

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.