Нам часто приходится повторять действия.
Например, выводить товары из списка друг за другом или просто запускать один и тот же код для каждого числа от 1 до 10.
Циклы — это способ повторения одного и того же кода несколько раз.
Циклы for…of и for…in
Небольшой анонс для продвинутых читателей.
В этой статье рассматриваются только базовые циклы: while
, do..while
и for(..;..;..)
.
Если вы пришли к этой статье в поисках других типов циклов, вот указатели:
См.…in для перебора свойств объекта.
См. «…of» и «iterables» для циклического перебора массивов и итерируемых объектов.
В противном случае, пожалуйста, читайте дальше.
Цикл while
имеет следующий синтаксис:
пока (условие) { // код // так называемое «тело цикла» }
Пока condition
истинно, code
из тела цикла выполняется.
Например, приведенный ниже цикл выводит i
while i < 3
:
пусть я = 0; while (i < 3) { // показывает 0, затем 1, затем 2 предупреждение(я); я++; }
Однократное выполнение тела цикла называется итерацией . Цикл в приведенном выше примере выполняет три итерации.
Если бы i++
отсутствовал в приведенном выше примере, цикл повторялся бы (теоретически) вечно. На практике браузер предоставляет способы остановить такие циклы, а в серверном JavaScript мы можем завершить этот процесс.
Условием цикла может быть любое выражение или переменная, а не только сравнение: условие оценивается и преобразуется в логическое значение с помощью while
.
Например, более короткий способ записи while (i != 0)
— while (i)
:
пусть я = 3; while (i) { // когда i становится 0, условие становится ложным и цикл останавливается предупреждение(я); я--; }
Фигурные скобки не требуются для однострочного тела.
Если тело цикла содержит один оператор, мы можем опустить фигурные скобки {…}
:
пусть я = 3; в то время как (i) alert(i--);
Проверку условия можно переместить ниже тела цикла, используя синтаксис do..while
:
делать { // тело цикла } Пока (условие);
Цикл сначала выполнит тело, затем проверит условие и, пока оно истинно, выполнит его снова и снова.
Например:
пусть я = 0; делать { предупреждение(я); я++; } Пока (я <3);
Эту форму синтаксиса следует использовать только в том случае, если вы хотите, чтобы тело цикла выполнялось хотя бы один раз, независимо от того, является ли условие истинным. Обычно предпочтительна другая форма: while(…) {…}
.
Цикл for
более сложен, но он также является наиболее часто используемым циклом.
Это выглядит так:
для (начало; условие; шаг) { // ... тело цикла ... }
Давайте узнаем значение этих частей на примере. Цикл ниже запускает alert(i)
для i
от 0
до (но не включая) 3
:
for (let i = 0; i < 3; i++) { // показывает 0, затем 1, затем 2 предупреждение (я); }
Давайте рассмотрим оператор for
по частям:
часть | ||
---|---|---|
начинать | let i = 0 | Выполняется один раз при входе в цикл. |
состояние | i < 3 | Проверяется перед каждой итерацией цикла. Если false, цикл останавливается. |
тело | alert(i) | Выполняется снова и снова, пока условие истинно. |
шаг | i++ | Выполняется после тела на каждой итерации. |
Общий алгоритм цикла работает следующим образом:
Начало запуска → (если условие → тело выполнения и шаг выполнения) → (если условие → тело выполнения и шаг выполнения) → (если условие → тело выполнения и шаг выполнения) → ...
То есть, begin
выполняется один раз, а затем повторяется: после каждой проверки condition
выполняются body
и step
.
Если вы новичок в циклах, возможно, вам будет полезно вернуться к примеру и воспроизвести его выполнение шаг за шагом на листе бумаги.
Вот что именно происходит в нашем случае:
// для (пусть i = 0; i < 3; i++) alert(i) // начало запуска пусть я = 0 // если условие → выполнить тело и выполнить шаг если (я <3) {предупреждение (я); я++ } // если условие → выполнить тело и выполнить шаг если (я <3) {предупреждение (я); я++ } // если условие → выполнить тело и выполнить шаг если (я <3) {предупреждение (я); я++ } // ...закончим, потому что теперь i == 3
Объявление встроенной переменной
Здесь переменная «счетчик» i
объявляется прямо в цикле. Это называется «встроенным» объявлением переменной. Такие переменные видны только внутри цикла.
для (пусть я = 0; я <3; я++) { предупреждение (я); // 0, 1, 2 } предупреждение (я); // ошибка, нет такой переменной
Вместо определения переменной мы могли бы использовать существующую:
пусть я = 0; for (i = 0; i < 3; i++) { // используем существующую переменную предупреждение (я); // 0, 1, 2 } предупреждение (я); // 3, видимый, поскольку объявлен вне цикла
Любую часть for
можно пропустить.
Например, мы можем опустить begin
, если нам не нужно ничего делать в начале цикла.
Как здесь:
пусть я = 0; // мы уже объявили и назначили for (; i < 3; i++) { // «начать» не нужно предупреждение(я); // 0, 1, 2 }
Мы также можем удалить часть step
:
пусть я = 0; для (; я < 3;) { предупреждение (я++); }
Это делает цикл идентичным while (i < 3)
.
Фактически мы можем удалить всё, создав бесконечный цикл:
для (;;) { // повторяется без ограничений }
Обратите внимание, что две точки for
запятой ;
должен присутствовать. В противном случае будет синтаксическая ошибка.
Обычно цикл завершается, когда его условие становится ложным.
Но мы можем принудительно выйти в любой момент, используя специальную директиву break
.
Например, приведенный ниже цикл запрашивает у пользователя серию чисел и «обрывается», когда число не введено:
пусть сумма = 0; в то время как (истина) { let value = +prompt("Введите число", ''); if (!value) сломать; // (*) сумма += значение; } Предупреждение('Сумма:' + сумма);
Директива break
активируется в строке (*)
если пользователь вводит пустую строку или отменяет ввод. Он немедленно останавливает цикл, передавая управление первой строке после цикла. А именно, alert
.
Комбинация «бесконечный цикл + break
по необходимости» отлично подходит для ситуаций, когда состояние цикла необходимо проверять не в начале или конце цикла, а в середине или даже в нескольких местах его тела.
Директива continue
— это «облегченная версия» break
. Это не останавливает весь цикл. Вместо этого он останавливает текущую итерацию и заставляет цикл начать новую (если позволяет условие).
Мы можем использовать его, если закончили текущую итерацию и хотим перейти к следующей.
Цикл ниже использует continue
вывода только нечетных значений:
для (пусть я = 0; я < 10; я++) { // если true, пропускаем оставшуюся часть тела если (i % 2 == 0) продолжить; предупреждение (я); // 1, затем 3, 5, 7, 9 }
Для четных значений i
директива continue
прекращает выполнение тела и передает управление следующей итерации for
(со следующим номером). Таким образом, alert
вызывается только для нечетных значений.
Директива continue
помогает уменьшить вложенность
Цикл, отображающий нечетные значения, может выглядеть так:
для (пусть я = 0; я < 10; я++) { если (я % 2) { предупреждение(я); } }
С технической точки зрения это идентично приведенному выше примеру. Конечно, мы можем просто обернуть код в блок if
вместо использования continue
.
Но в качестве побочного эффекта это создало еще один уровень вложенности (вызов alert
внутри фигурных скобок). Если код внутри if
длиннее нескольких строк, это может ухудшить общую читабельность.
Без break/continue
вправо от '?'
Обратите внимание, что синтаксические конструкции, не являющиеся выражениями, не могут использоваться с тернарным оператором ?
. В частности, здесь не разрешены такие директивы, как break/continue
.
Например, если мы возьмем этот код:
если (я > 5) { предупреждение (я); } еще { продолжать; }
…и перепишите его, используя вопросительный знак:
(я > 5) ? предупреждение(i): продолжить; // продолжение здесь запрещено
…перестает работать: синтаксическая ошибка.
Это еще одна причина не использовать оператор вопросительного знака ?
вместо if
.
Иногда нам нужно выйти из нескольких вложенных циклов одновременно.
Например, в приведенном ниже коде мы перебираем i
и j
, запрашивая координаты (i, j)
от (0,0)
до (2,2)
:
для (пусть я = 0; я <3; я++) { for (пусть j = 0; j < 3; j++) { let input = Prompt(`Значение по координатам (${i},${j})`, ''); // что, если мы захотим выйти отсюда к пункту «Готово» (ниже)? } } Предупреждение('Готово!');
Нам нужен способ остановить процесс, если пользователь отменяет ввод.
Обычный break
после input
приведет только к разрыву внутреннего цикла. Этого недостаточно – этикетки, приходите на помощь!
Метка — это идентификатор с двоеточием перед циклом:
labelName: для (...) { ... }
Оператор break <labelName>
в приведенном ниже цикле переходит к метке:
внешний: for (пусть я = 0; я <3; я++) { for (пусть j = 0; j < 3; j++) { let input = Prompt(`Значение по координатам (${i},${j})`, ''); // если пустая строка или отменена, то выходим из обоих циклов if (!input) сломать внешний; // (*) // делаем что-то со значением... } } Предупреждение('Готово!');
В приведенном выше коде команда break outer
ищет метку с именем outer
вверху и выходит из этого цикла.
Таким образом, элемент управления переходит прямо от (*)
к alert('Done!')
.
Мы также можем переместить метку на отдельную строку:
внешний: for (пусть i = 0; i < 3; i++) { ... }
Директиву continue
также можно использовать с меткой. В этом случае выполнение кода переходит к следующей итерации помеченного цикла.
Этикетки не позволяют никуда «прыгнуть»
Метки не позволяют нам перейти в произвольное место кода.
Например, невозможно сделать следующее:
сломать этикетку; // переход к метке ниже (не работает) этикетка: для (...)
Директива break
должна находиться внутри блока кода. Технически подойдет любой помеченный блок кода, например:
этикетка: { // ... сломать этикетку; // работает // ... }
…Хотя, как мы видели в примерах выше, 99,9% времени break
используется внутри циклов.
continue
возможно только внутри цикла.
Мы рассмотрели 3 типа циклов:
while
– условие проверяется перед каждой итерацией.
do..while
– условие проверяется после каждой итерации.
for (;;)
— условие проверяется перед каждой итерацией, доступны дополнительные настройки.
Для создания «бесконечного» цикла обычно используется конструкция while(true)
. Такой цикл, как и любой другой, можно остановить с помощью директивы break
.
Если мы не хотим ничего делать в текущей итерации и хотим перейти к следующей, мы можем использовать директиву continue
.
break/continue
поддержки меток перед циклом. Метка — это единственный способ для break/continue
выйти из вложенного цикла и перейти к внешнему.
важность: 3
Каково последнее значение, оповещаемое этим кодом? Почему?
пусть я = 3; в то время как (i) { Предупреждение(я--); }
Ответ: 1
.
пусть я = 3; в то время как (i) { предупреждение( я--); }
Каждая итерация цикла уменьшает i
на 1
. Проверка while(i)
останавливает цикл, когда i = 0
.
Следовательно, шаги цикла образуют следующую последовательность («развернутый цикл»):
пусть я = 3; предупреждение(я--); // показывает 3, уменьшает i до 2 alert(i--) // показывает 2, уменьшает i до 1 alert(i--) // показывает 1, уменьшает i до 0 // выполнено, проверка while(i) останавливает цикл
важность: 4
Для каждой итерации цикла запишите, какое значение он выводит, а затем сравните его с решением.
Оба цикла alert
об одних и тех же значениях или нет?
Форма префикса ++i
:
пусть я = 0; while (++i <5) alert(i);
Постфиксная форма i++
пусть я = 0; while (i++ <5) alert(i);
Задача демонстрирует, как постфиксно-префиксные формы могут привести к разным результатам при использовании в сравнениях.
От 1 до 4
пусть я = 0; while (++i <5) alert(i);
Первое значение — i = 1
, поскольку ++i
сначала увеличивает i
, а затем возвращает новое значение. Итак, первое сравнение — 1 < 5
, а alert
показывает 1
.
Затем следуют 2, 3, 4…
– значения появляются одно за другим. При сравнении всегда используется увеличенное значение, поскольку перед переменной стоит ++
.
Наконец, i = 4
увеличивается до 5
, сравнение while(5 < 5)
завершается неудачно, и цикл останавливается. Поэтому 5
не отображается.
От 1 до 5
пусть я = 0; while (i++ <5) alert(i);
Первое значение снова i = 1
. Постфиксная форма i++
увеличивает i
а затем возвращает старое значение, поэтому при сравнении i++ < 5
будет использоваться i = 0
(в отличие от ++i < 5
).
Но alert
звонок отдельный. Это еще один оператор, который выполняется после приращения и сравнения. Таким образом, он получает текущий i = 1
.
Затем следуйте 2, 3, 4…
Остановимся на i = 4
. Форма префикса ++i
будет увеличивать его и использовать 5
при сравнении. Но здесь мы имеем постфиксную форму i++
. Таким образом, он увеличивает i
до 5
, но возвращает старое значение. Следовательно, на самом деле сравнение while(4 < 5)
– true, и элемент управления продолжает выдавать alert
.
Значение i = 5
является последним, поскольку на следующем шаге while(5 < 5)
будет ложным.
важность: 4
Для каждого цикла запишите, какие значения он будет показывать. Затем сравните с ответом.
Оба цикла alert
одинаковых значениях или нет?
Постфиксная форма:
for (пусть i = 0; i < 5; i++) alert( i );
Форма префикса:
for (пусть i = 0; i < 5; ++i) alert( i );
Ответ: от 0
до 4
в обоих случаях.
for (пусть i = 0; i < 5; ++i) alert( i ); for (пусть i = 0; i < 5; i++) alert( i );
Это можно легко вывести из алгоритма for
:
Выполнить один раз i = 0
перед всем (начать).
Проверьте условие i < 5
Если true
– выполнить тело цикла alert(i)
, а затем i++
Приращение i++
отделено от проверки условия (2). Это просто еще одно заявление.
Значение, возвращаемое приращением, здесь не используется, поэтому нет никакой разницы между i++
и ++i
.
важность: 5
Используйте цикл for
для вывода четных чисел от 2
до 10
.
Запустить демо-версию
for (пусть я = 2; я <= 10; я++) { если (я % 2 == 0) { предупреждение(я); } }
Мы используем оператор «по модулю» %
, чтобы получить остаток и проверить здесь четность.
важность: 5
Перепишите код, изменив цикл for
на while
не меняя его поведения (выходные данные должны остаться прежними).
для (пусть я = 0; я <3; я++) { alert(`номер ${i}!`); }
пусть я = 0; в то время как (я <3) { alert(`номер ${i}!`); я++; }
важность: 5
Напишите цикл, который запрашивает число больше 100
. Если посетитель вводит другой номер – попросите его ввести еще раз.
Цикл должен запрашивать число до тех пор, пока посетитель не введет число больше 100
или не отменит ввод/введет пустую строку.
Здесь можно предположить, что посетитель вводит только цифры. В этой задаче нет необходимости реализовывать специальную обработку нечисловых входных данных.
Запустить демо-версию
пусть число; делать { num = Prompt("Введите число больше 100?", 0); } while (num <= 100 && num);
Цикл do..while
.. while повторяется, пока обе проверки правдивы:
Проверка на num <= 100
– то есть введенное значение все еще не больше 100
.
Проверка && num
является ложной, если num
имеет значение null
или пустую строку. Затем цикл while
тоже останавливается.
PS Если num
равно null
, то num <= 100
имеет true
, поэтому без второй проверки цикл не остановится, если пользователь нажмет кнопку ОТМЕНА. Обе проверки обязательны.
важность: 3
Целое число больше 1
называется простым, если его нельзя разделить без остатка ни на что, кроме 1
и самого себя.
Другими словами, n > 1
является простым числом, если его нельзя разделить без остатка ни на что, кроме 1
и n
.
Например, 5
— простое число, потому что его нельзя разделить без остатка на 2
, 3
и 4
.
Напишите код, который выводит простые числа в интервале от 2
до n
.
Для n = 10
результатом будет 2,3,5,7
.
PS Код должен работать для любого n
, а не жестко настраиваться под какое-либо фиксированное значение.
Существует множество алгоритмов решения этой задачи.
Давайте воспользуемся вложенным циклом:
Для каждого i в интервале { проверьте, есть ли у меня делитель от 1..i если да => значение не является простым если нет => значение является простым, покажите его }
Код с использованием метки:
пусть n = 10; следующийПрайм: for (let i = 2; i <= n; i++) { // для каждого i... for (let j = 2; j < i; j++) { // ищем делитель.. если (i % j == 0) продолжить nextPrime; // не простое число, идем дальше i } предупреждение(я); // простое число }
Есть много возможностей для оптимизации. Например, мы могли бы искать делители от 2
до квадратного корня из i
. Но в любом случае, если мы хотим быть действительно эффективными для больших интервалов, нам нужно изменить подход и полагаться на сложную математику и сложные алгоритмы, такие как квадратичное решето, решето общего числового поля и т. д.