Цикл - это разновидность управляющей конструкции в высокоуровневых языках программирования, предназначенная для организации многократного исполнения набора инструкций (многократного повторения одного участка кода).
Также циклом может называться любая многократно исполняемая последовательность инструкций, организованная любым способом (например, с помощью условного перехода).
Одно выполнение тела цикла называется итерацией.
Виды циклов:
- Безусловный (бесконечный) цикл - цикл, выход из которых не предусмотрен логикой программы. Специальных синтаксических средств для создания бесконечных циклов языки программирования не предусматривают, поэтому такие циклы создаются с помощью конструкций, предназначенных для создания обычных (или условных) циклов. Для обеспечения бесконечного повторения проверка условия в таком цикле либо отсутствует (если позволяет синтаксис), либо заменяется константным значением.
- Цикл с предусловием — цикл, который выполняется, пока истинно некоторое условие, указанное перед его началом. Это условие проверяется до выполнения тела цикла, поэтому тело может быть не выполнено ни разу (если условие с самого начала ложно). В большинстве процедурных языков программирования реализуется оператором while, отсюда его второе название — while-цикл.
- Цикл с постусловием — цикл, в котором условие проверяется после выполнения тела цикла. Отсюда следует, что тело всегда выполняется хотя бы один раз.
- Цикл с выходом из середины — наиболее общая форма условного цикла. Синтаксически такой цикл оформляется с помощью трёх конструкций:
- начала цикла (маркирует точку программы, в которой начинается тело цикла);
- конца цикла (маркирует точку, где тело заканчивается);
- команды выхода из цикла (при выполнении которой цикл заканчивается и управление передаётся на оператор, следующий за конструкцией конца цикла). Для того чтобы цикл выполнился более одного раза, команда выхода должна вызываться не безусловно, а только при выполнении условия выхода из цикла.
- Цикл со счётчиком — цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. В большинстве процедурных языков программирования реализуется оператором for, в котором указывается счётчик (так называемая «переменная цикла»), требуемое количество проходов (или граничное значение счётчика) и, возможно, шаг, с которым изменяется счётчик.
- Совместный цикл (цикл по коллекции, цикл просмотра) - цикл, задающий выполнение некоторой операции для объектов из заданного множества, без явного указания порядка перечисления этих объектов. Совместный цикл, теоретически, никак не определяет, в каком порядке операция будет применяться к элементам множества, хотя конкретные языки программирования, разумеется, могут задавать конкретный порядок перебора элементов. Произвольность даёт возможность оптимизации исполнения цикла за счёт организации доступа не в заданном программистом, а в наиболее выгодном порядке.
Замечание: принципиальное отличие цикла с выходом из середины от других условных циклов:
- часть тела цикла, расположенная после начала цикла и до команды выхода, выполняется всегда (даже если условие выхода из цикла истинно при первой итерации);
- часть тела цикла, находящаяся после команды выхода, не выполняется при последней итерации.
Цикл while
Цикл while имеет следующий синтаксис:
1 2 3 |
while (condition) { // condition - это условие цикла // код ("тело цикла") } |
Код из тела цикла выполняется, пока условие condition истинно (true).
Условием цикла может быть любое выражение или переменная: условие while вычисляется и преобразуется в логическое значение.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// такая запись цикла выведет числа от 0 до 9 let i = 0; while (i < 10) { console.log(i); i++; // постинкремент увеличивает значение со следующей строки } // такая запись цикла выведет числа от 1 до 10 let i = 0; while (i++ < 10) { // постинкремент увеличивает значение со следующей строки console.log(i); } // такая запись цикла выведет числа от 9 до 0 (т.к. 0 - это false и цикл остановится) let i = 10; while (i--) { // постдекремент уменьшает значение со следующей строки console.log(i); } |
Тело цикла из одной строки (из одной инструкции) не требует фигурных скобок {…}:
1 2 |
let i = 10; while (i) alert(i--); |
Цикл do … while
Обеспечивает проверку условия, расположенного под телом цикла. Сначала выполняется тело цикла, а затем проверяется условие condition: пока его значение равно true, тело цикла выполняется снова и снова.
1 2 3 4 5 |
let a = 10; do { console.log(a); // выведет в консоль числа от 10 до 0 включительно } while (a--); |
Форма цикла do ... while оправдана, если вы хотите чтобы тело цикла выполнилось хотя бы один раз, даже если условие окажется ложным. На практике чаще используется форма с предусловием: while(…) {…}.
Цикл for
Цикл for состоит из 3 необязательных выражений в круглых скобках, разделённых точками с запятой, и имеет следующий синтаксис:
1 2 3 |
for ([инициализация]; [условие]; [финальное выражение]){ тело цикла } |
- [инициализация]
- [условие] (true)
- тело цикла
- [финальное выражение]
- [условие] (true) ...
- тело цикла
- [финальное выражение]
- [условие] (false)
- end.
Инициализация - это выражение (в том числе выражения присвоения) или определение переменных. Обычно используется:
- для инициализации счётчика (если переменная была объявлена выше по коду);
- для встроенного (опционального) объявления новых переменных с помощью ключевого слова:
- var - видимы вне цикла в той же области области видимости, что и цикл for;
- let - видимы только внутри цикла.
Результат выражения [инициализация] отбрасывается.
Условие - это выражение, выполняющееся на каждой итерации цикла:
- если [условие] истинно, цикл выполняется;
- если выражения [условие] нет, то условие всегда считается истиной.
- если [условие] ложно, выполнение переходит к первому выражению, следующему за for.
Условие не является обязательным.
Финальное выражение - это выражение, выполняющееся в конце итерации цикла, перед следующим выполнением условия. Обычно используется для обновления или увеличения переменной счётчика.
Тело цикла - выражение, которое выполняется, когда условие цикла истинно. Чтоб выполнить множество выражений в цикле, используется блок ({ ... }) для группировки этих выражений. Чтобы не выполнять никакого выражения в цикле, используется пустое выражение (;).
Любая часть цикла for может быть пропущена, при этом сами точки с запятой ";" при записи обязательны (иначе будет ошибка синтаксиса).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// В этом примере будут выведены все четные числа от 0 до 10 (включительно) for (let i = 0; i <= 10; i++) { if (i % 2 === 0) { console.log(i); } } // В этом примере будут выведены все числа от 0 до 9 кроме 5 for (let b = 0; b < 10; b++) { if (b === 5) { continue; // при b===5 цикл перейдет на следующую итерацию, пропустив шаг (действия в цикле) } console.log(b); } // В этом примере будут выведены числа от 0 до 4 включительно for (let m = 0; m < 10; m++) { if (m === 5) { console.log("Сработал break"); break; // при b===5 цикл остановится } console.log(m); } |
Переменная, объявленная в заголовке цикла for с помощью ключевого слова var – одна на все итерации цикла: она не может быть блочной или локальной внутри цикла и видна даже после выполнения цикла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// var не может быть блочной или локальной внутри цикла: var result = []; for (var i = 0; i < 5; i++) { result[i] = function() { console.log(i); }; } result[0](); // 5 (ожидаем 0, но т.к. var - не локальная, то в момент вызова result[]() i = 5 ) result[1](); // 5 (ожидаем 1) result[2](); // 5 (ожидаем 2) result[3](); // 5 (ожидаем 3) result[4](); // 5 (ожидаем 4) // Ожидаемый результат с использованием let for (let i = 0; i < 5; i++) { result[i] = function() { console.log(i); }; } result[0](); // 0 (ожидаем 0) result[1](); // 1 (ожидаем 1) result[2](); // 2 (ожидаем 2) result[3](); // 3 (ожидаем 3) result[4](); // 4 (ожидаем 4) |
Переменная, объявленная в заголовке цикла for с помощью ключевого слова let, будет объявлена для каждой итерации (а не один раз для всего цикла). Каждому повторению цикла соответствует своя независимая переменная let. Если внутри цикла есть вложенные объявления функций, то в замыкании каждой из них будет та переменная, которая была при соответствующей итерации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Ожидаемый результат с использованием let for (let i = 0; i < 5; i++) { result[i] = function() { console.log(i); }; } result[0](); // 0 (ожидаем 0) result[1](); // 1 (ожидаем 1) result[2](); // 2 (ожидаем 2) result[3](); // 3 (ожидаем 3) result[4](); // 4 (ожидаем 4) // var не может быть блочной или локальной внутри цикла: var result = []; for (var i = 0; i < 5; i++) { result[i] = function() { console.log(i); }; } result[0](); // 5 (ожидаем 0, но т.к. var - не локальная, то в момент вызова result[]() i = 5 ) result[1](); // 5 (ожидаем 1) result[2](); // 5 (ожидаем 2) result[3](); // 5 (ожидаем 3) result[4](); // 5 (ожидаем 4) |
Перебор символов строки с помощью цикла for:
1 2 3 4 5 6 7 |
let str = "Hello"; let res = ""; for (let c = 0; c < str.length; c++) { res += str[c] + "*"; // console.log(res); } console.log(res); |
Перебор элементов массива циклом for:
1 2 3 4 5 6 |
res = "*"; let arr = ["black", "blue", "yellow", "orange"]; for (let d = 0; d < arr.length; d++) { res = arr[d].toUpperCase(); console.log(res); } |
Прерывание цикла «break»
Директива break позволяет выйти из цикла в любой момент: она полностью прекращает выполнение цикла и передаёт управление на строку за его телом.
Сочетание «бесконечный цикл + break» используется в ситуациях, когда условие, по которому нужно прерваться, находится не в начале или конце цикла, а посередине.
Переход к следующей итерации "continue"
Директива continue – это «облегчённая версия» break.
По сравнению с инструкцией break, continue прерывает выполнение цикла не полностью:
- в цикле while оно переносит поток выполнения к условию;
- в цикле for оно переносит поток выполнения к финальному выражению в описании цикла.
Таким образом, при её выполнении цикл не прерывается, а переходит к следующей итерации (если условие все ещё равно true).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// В этом примере будут выведены все числа от 0 до 9 кроме 5 for (let a = 0; a < 10; a++) { if (a === 5) { continue; // при a===5 цикл перейдет на следующую итерацию, пропустив шаг (действия в цикле) } console.log(a); } // В этом примере будут выведены только четные значения for (let i = 0; i < 10; i++) { if (i % 2 == 0) continue; // четные - пропускаем alert(i); // 1, затем 3, 5, 7, 9 } |
Директива continue позволяет избегать вложенности.
Нельзя использовать break/continue справа от оператора „?“.
Метки для break/continue
Бывает, нужно выйти одновременно из вложенного уровня цикла (сложного цикла). Обычный break прервёт лишь внутренний цикл, а внешний - продолжится. Достичь желаемого поведения можно с помощью меток.
Метка – это именованный идентификатор, располагающийся выше цикла и позволяющий выйти из цикла (вложенного уровня сложного цикла) в начало цикла на строку с указанным идентификатором.
1 2 3 |
labelName: for (...) { ... } |
Вызов break Outer в цикле ниже ищет ближайший внешний цикл с такой меткой и переходит в его конец.
1 2 3 4 5 6 7 8 |
Outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Значение на координатах (${i},${j})`, ''); if (!input) break Outer; // если пустая строка или Отмена, то выйти из обоих циклов ... // какие-либо действия } } alert('Готово!'); |
В примере выше это означает, что вызовом break Outer будет разорван внешний цикл до метки с именем Outer, и управление перейдёт к строке alert('Готово!').
Можно размещать метку на отдельной строке:
1 2 |
outer: for (let i = 0; i < 3; i++) { ... } |
Директива continue также может быть использована с меткой. В этом случае управление перейдёт на следующую итерацию цикла с меткой.
Метки не дают возможности передавать управление в произвольное место кода.
Вызов директив break и continue возможен только внутри цикла, а метка должна находиться в коде выше этих директив.
Цикл for ... in и for ... of
Цикл for … in
Цикл for … in применяется для перебора всех перечисляемых свойств объекта, и имеет следующий синтаксис:
1 2 3 |
for (key in object) { ... // тело цикла, выполняется для каждого свойства объекта } |
- key - переменная, получающая очередное имя свойства, назначаемое на каждой итерации;
- object - объект, свойства которого перебираются циклом.
Пример:
1 2 3 4 5 6 7 8 |
let myArray = [3, 5, 7]; myArray.foo = "hello"; // добавляем в объект свойство foo со значением 'hello' myArray[1] = "goodbay"; // изменяем в массиве значение элемента с индексом с "5" на "goodbay" console.log(myArray); // [3, "goodbay", 7, foo: "hello"] for (let property in myArray) { console.log(property); // выведет 0, 1, 2, "foo" - т.е. имена свойств (индексы элементов массива) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
let user = { firstName: "Alex", isAdmin: true, "user-adress": { country: "Russia", sity: "Moscow", }, skills: ["html", "css", "JS"], getFoo: { value: function () { return this.foo; }, }, }; for (let property in user) { console.log(property); // firstName, isAdmin, user-adress, skills, getFoo } // ИЛИ for (let property in user.firstName) { console.log(property); // 0, 1, 2, 3 (каждый символ строки имеет свой индекс) } // ИЛИ for (let property in user.skills) { console.log(property); // 0, 1, 2 (каждый элемент массива имеет свой индекс) } // ИЛИ for (let property in user["user-adress"]) { console.log(property); // country, sity } // ИЛИ for (let property in user.getFoo) { console.log(property); // value } |
Цикл for...in проходит только по перечисляемым свойствам объекта в произвольном порядке: for...in не следует использовать для Array, где важен порядок индексов (с числовыми свойствами или индексами массивов, когда важен порядок доступа к свойствам, лучше использовать циклы for, Array.prototype.forEach() или for...of). Свойства, добавленные в объект при выполнении цикла, могут быть пропущены. Не следует добавлять, изменять или удалять свойство объекта во время итерации (если цикл по нему ещё не прошёл).
Объекты, созданные встроенными конструкторами, такими как Array и Object имеют неперечисляемые свойства от Object.prototype и String.prototype, например, от String - это indexOf(), а от Object - метод toString(). Цикл пройдёт по всем перечисляемым свойствам объекта, а также тем, что он унаследует от конструктора прототипа (свойства объекта в цепи прототипа). Подробнее о перечисляемых и не перечисляемых свойствах.
Для получения массива со всеми свойствами объекта (независимо от того, перечисляемые они или нет) используйте метод Reflect.ownKeys() или getOwnPropertyNames().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
let user = { firstName: "Alex", isAdmin: true, "user-adress": { country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"], getFoo: { value: function () { return this.foo; }, }, [Symbol()]: "Symbol()" }; console.log(Object.getOwnPropertyNames(user)); // (5) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo'] // Reflect.ownKeys() выводит также и ключи Symbol console.log(Reflect.ownKeys(user)); // (6) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo', Symbol()] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
let user = { firstName: "Alex", lastName: "NAV", isAdmin: true, "user-adress": { country: "Russia", sity: "Moscow", }, skills: ["html", "css", "JS"], }; //выведет имена свойств объекта user (вложенный объект "user-adress" игнорируется) for (let property in user) { console.log(property); // firstName,lastName,isAdmin,user-adress,skills } // выведет имена свойств вложенного объекта "user-adress" for (let property in user["user-adress"]) { console.log(property); // country,sity } |
Цикл for...of
Оператор for...of выполняет цикл обхода итерируемых объектов (включая Array, Map, Set, объект arguments и подобных), вызывая на каждом шаге итерации значения для каждого свойства объекта.
Подробнее об итерируемых объектах и итераторе.
Синтаксис цикла for...of:
1 2 3 |
for (variable of object) { statement } |
- variable - на каждом шаге итерации переменной variable присваивается значение нового свойства итерируемого объекта object (переменная variable может быть объявлена с помощью const, let или var);
- object - объект, перечисляемые свойства которого обходятся во время выполнения цикла.
Если объект неитерируемый, движок вернет ошибку "... is not iterable".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
let user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow", }, skills: ["html", "css", "JS"], getFoo: { value: function() { return this.foo; }} }; for (let property of user['user-adress']) { console.log(property); //! user.user-adress is not iterable } for (let property of user.getFoo) { console.log(property); //! user.getFoo is not iterable } // НО!!! for (let property of user.skills) { console.log(property); // html, css, JS } |
Примеры использования цикла for...of:
1 2 3 4 5 6 |
let iterable = [10, 20, 30]; for (let value of iterable) { value += 1; console.log(value);// 11, 21, 31 } |
Допускается использовать const вместо let, если не нужно переназначать переменные (изменять значения элементов массива) внутри цикла:
1 2 3 4 5 |
let iterable = [10, 20, 30]; for (const value of iterable) { console.log(value);// 10, 20, 30 } |
1 2 3 4 5 |
let myString = 'foo'; for (let value of myString) { console.log(value); // "f", "o", "o" } |
1 2 3 4 5 6 7 8 9 10 |
let myMap = new Map([['a', 1], ['b', 2], ['c', 3]]); for (let entry of myMap) { console.log(entry); // ['a', 1] , ['b', 2] , ['c', 3] } for (let [key, value] of myMap) { console.log(key); // a, b, c console.log(value); // 1, 2, 3 } |
1 2 3 4 5 |
let mySet = new Set([1, 1, 2, 2, 3, 3]); for (let value of mySet) { console.log(value); // 1, 2, 3 } |
1 2 3 4 5 |
(function () { for (let value of arguments) { console.log(value); // 1, 'two', 3 } })(1, "two", 3); |
Обход DOM коллекций наподобие NodeList. Следующий пример добавляет класс read параграфам, являющимся непосредственными потомками статей:
1 2 3 4 5 6 7 |
// Примечание: работает только на платформах, где // реализован NodeList.prototype[Symbol.iterator] let articleParagraphs = document.querySelectorAll("article > p"); for (let paragraph of articleParagraphs) { paragraph.classList.add("read"); } |
В циклах for...of аварийный выход осуществляется через break, throw или return. Во всех вариантах итератор завершается.
Пример использования циклов for ... in и for ... of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
const users = [ { name: "Alex", age: 40 }, { name: "Olga", age: 30 }, { name: "Serg", age: 20 }, { name: "Mary", age: 15 } ]; // Цикл for ... in - для перебора доступных (enumerable) свойств объекта (массива) for (let key in users) { console.log(key); // выведет построчно 0 1 2 3 console.log(users[key]); // выведет построчно объекты Object { name: "...", age: ... } } // Для сравнения - цикл for ... of ////////////////////////////////////////// for (let key of users){ console.log(key) // выведет объекты Object { name: "...", age: ... } console.log(users[key]); // undefined } |