Назначение и виды циклов

Циклы – это конструкции языка JavaScript, которые применяются, когда необходимо выполнить некоторые действия несколько раз.

На языке JavaScript существуют различные виды циклов, которые по сути дела выполняют одно и тоже. Просто одни задачи решаются более просто с помощью одних циклов, а другие – с помощью других.

В JavaScript можно выделить следующие виды циклов:

Цикл for

Данный цикл в основном используется когда известно точное количество повторений. Этот цикл ещё называют циклом со счётчиком.

Синтаксис цикла «for»:

for (инициализация; условие; финальное выражение) {
  /* тело цикла */
}
Алгоритм работы цикла for в JavaScript

Основные части конструкции цикла «for»:

  • инициализация - это выражение, которое выполняется один раз перед выполнением цикла; обычно используется для инициализации счётчика;
  • условие - это выражение, истинность которого проверяется перед каждой итерацией; если выражение вычисляется как истина, то выполняется итерация; в противном случае цикл «for» завершает работу;
  • финальное выражение - это выражение, которое выполняется в конце каждой итерации; обычно используется для изменения счетчика;
  • тело цикла - инструкции, выполнение которых нужно повторять.

Рассмотрим пример цикла, который выведет в консоль числа от 1 до 8:

// цикл «for» от 1 до 8, с шагом 1
for (var i = 1; i <= 8; i++) {
  console.log(i);
}

В этом примере:

  • инициализация: var i = 1 (объявление переменной i и присвоение ей значения 1);
  • условие выполнения цикла: i <= 8 (пока значение переменной i меньше или равно 8);
  • финальное выражение, которое нужно выполнять в конце каждой итерации: i++ (увеличение значение переменной i на 1);
  • инструкция, которую нужно выполнять: console.log(i) (выведение значения счётчика в консоль).

При этом если тело цикла состоит из одной инструкции, то её можно не заключать в фигурные скобки.

Таким образом, пример, приведённый выше, можно записать ещё так:

// цикл «for» от 1 до 8, с шагом 1
for (var i = 1; i <= 8; i++) console.log(i);

Необязательные части цикла цикла «for».

В «for» все части цикла являются не обязательными.

Например, можно пропустить выражение инициализации:

var i = 1;
// цикл «for»
for (; i <= 8; i++) {
  console.log(i);
}

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

Условие в «for» тоже является не обязательным. Без условия цикл будет выполняться бесконечное количество раз. В этом случае чтобы его прервать (выйти из цикла) необходимо использовать инструкцию break.

// цикл «for»
for (var i = 1; ; i++) {
  if (i >= 8) { // условие прерывания цикла
    break;
  }
  console.log(i);
}

Финальное выражение в «for» также является не обязательным. Счётчик цикла в этом случае можно, например, изменять в теле.

// цикл «for»
for (var i = 1; i <= 8; ) {
  console.log(i);
  i++; // увеличение счетчика на 1
}

В «for» можно вообще опустить 3 выражения (бесконечный цикл):

var i = 1;
// цикл «for»
for (;;) {
  if (i >= 8) {
    break;
  }
  console.log(i);
  i++;
}

Кроме этого, в качестве тела цикла «for» можно использовать пустое выражение (;). Это используется, когда вам не нужно выполнять ни одной инструкции.

Например:

var
  arrA = [8, 12, 24],
  arrB = [];
for (i = 0; i < arrA.length; arrB[i] = arrA[i++] / 2) ;
console.log(arrB); // [4, 6, 12]

Пустое выражение в этом случае рекомендуется дополнительно снабжать комментарием:

// сумма чисел в массиве
var arr = [2, 7, 3];
for (var i = 0, length = arr.length, sum = 0; i < length; sum += arr[i++]) /* пустое выражение */ ;
// выведем сумму чисел в консоль:
console.log(sum); // 12

Пример использования цикла «for» для перебора элементов массива:

var arr = ["a", "b", "c"]; // массив
for (var i = 0, length = arr.length; i < length; i++) {
  console.log(arr[i]);
}

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

Пример на JavaScript - Вывод таблицы умножения в консоль с использованием вложенных циклов
var output = '';
for (var i = 1; i <= 9; i++) {
  for (var j = 1; j <= 9; j++) {
    output += ' ' + i * j;
    if (i * j < 10) {
      output += ' ';
    }
  }
  console.log(output);
  output = '';
}

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

Цикл while

Данный цикл предназначен для многократного выполнения одних и тех же инструкций до тех пор, пока истинно некоторое условие. Цикл «while» в основном используется, когда количество повторений заранее не известно.

while (условие) {
  /* тело цикла */
}
Алгоритм работы цикла while в JavaScript

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

Пример, в котором выведем в консоль чётные числа в диапазоне от 1 до 8:

// объявим переменную а и присвоим ей значение 1
var a = 1;
//цикл while с условием a <= 8
while (a <= 8) {
  // если число чётное (остаток от деления на 2 равен 0), то...
  if (a % 2 == 0) {
    // пропустим дальнейшее выполнение текущей итерации и перейдём к следующей
    continue;
  }
  // выведем значение переменной a в консоль
  console.log(a);
  // увеличим значение переменной a на 1
  a++;
}

Цикл do...while

Цикл «do...while», также как и цикл «while», выполняет одни и те же инструкции до тех пор, пока указанное условие истинно. Но в отличие от «while» в «do...while» условие проверяется после выполнения инструкций. Поэтому цикл «do...while» в любом случае выполнится не меньше одного раза, даже если условие изначально ложно.

Алгоритм работы цикла do...while в JavaScript
do {
  /* тело цикла */
} while (условие)

Пример, в котором выведем в консоль сумму чисел, которые будем запрашивать у пользователя с помощью функции prompt:

// num – переменная для хранения числа, введённого пользователем
// sum – переменная для хранения суммы чисел  
var num, sum = 0;
// цикл «do...while»
do {
  // запросим у пользователя данные и приведём их к числу
  num = +prompt ('Введите число', '');
  // если то, что ввёл пользователь после приведения является числом, то...
  if (num) {
    // прибавим к сумме число, введённое пользователем
    sum += data;
  }
// если num приводится к истине, то выполняем ещё итерацию
} while (num);  

Цикл for...in

Цикл «for...in» предназначен для перебора перечисляемых имён свойств объекта. В JavaScript свойство является перечисляемым, если его внутренний флаг [[Enumerable]] равен true.

Свойства объекта, которые не относятся к перечисляемым, в цикле не участвуют.

Например, объект (массив) созданный с использованием функции-конструктора Array или его литеральной записи имеет не перечисляемые свойства от Array.prototype и Object.prototype, такие как indexOf(), some(), toString() и др. Они не будут участвовать в цикле.

/* цикл для перебора всех перечисляемых свойств объекта
   - key – переменная, в которую будет помещаться имя свойства объекта 
   - object – объект, свойства которого нужно перебрать */
for (key in object) {
  /* тело цикла */
}

Переберём свойства объекта, созданного с помощью литеральной записи:

let car = {
  manufacturer: 'Ford',
  model: 'Fiesta',
  color: 'black'
};
for (let propName in car) {
  // propName – имя свойства
  // car[propName] – значение свойства
  console.log(propName + ' = ' + car[propName]);
}
// в консоль будет выведено: manufacturer = Ford, model = Fiesta, color = black

Кроме этого, следует отметить, что цикл for...in проходит не только по перечисляемых свойствам этого объекта, но и по наследуемым.

let item = {
  a: 1,
  b: 2
}
let newItem = Object.create(item);
newItem.c = 3;
newItem.d = 4;
for (let propName in newItem) {
  console.log(propName);
}
// в консоли будет выведено: a, b, c, d

Если вам наследуемые свойства не нужно учитывать, то их можно пропустить:

for (let propName in newItem) {
  // переходим к следующей итерации, если текущее свойство не принадлежит этому объекту
  if(!newItem.hasOwnProperty(propName)) {
    continue;
  }
  console.log(propName);
}
// в консоли будет выведено: c, d

Использование цикла for... in для перебора массива. В массиве свойствами являются числовые индексы.

// массив
var arr = ["Rock", "Jazz", "Classical", "Hip Hop"];
// перебор массива с помощью цикла for in
for (let index in arr) {
  // index - индекс элемента массива
  // arr[index] – значение элемента
  console.log(arr[index]);
}
// в результате в консоль будет выведено: "Rock", "Jazz", "Classical", "Hip Hop" 

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

При использовании цикла for…in стоит обратить внимание на то, что если вы к массиву добавили свои пользовательские свойства, то он по ним тоже пройдётся:

var arr = [5, 7, -3];
arr.sum = 2;
for (var key in arr) {
  console.log(arr[key]);
}
// в консоль будет выведено 5, 7, -3, 2

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

Использование цикла for…in для перебора символов в строке:

var str = 'Метод';
for (var key in str) {
  console.log(str[key]);
}
// М, е, т, о, д

Инструкции break и continue

Внутри тела цикла можно использовать специальные инструкции: break и continue.

Инструкция «break» предназначена для прекращения выполнения текущего цикла. Другими словами, она осуществляет выход и передачу управления инструкции, идущей после этого цикла.

Пример, в котором завершим цикл по перебору элементов массива, если его текущий элемент не будет являться числом:

// массив
var arr = [5, 3, "a", 4, "b", 16];
// цикл «for» для перебора массива arr
for (var i = 0, length = arr.length; i < length; i++) {
  // если текущий элемент массива не является числом, то...
  if (typeof arr[i] !== 'number') {
    // прерываем выполнение цикла
    break;
  }
  // выводим текущий элемент массива в консоль
  console.log(arr[i]);
}
// в результате в консоль будет выведено: 5, 3

Инструкция «continue» предназначена для прекращения дальнейшего выполнения кода и перехода к следующей итерации цикла.

Пример, в котором найдём в слове «программирование» символы «а» и «о», и выведем их позиции в консоль:

// строка
var str = 'программирование';
// цикл "for" для перебора символов строки 
for (var i = 0, length = str.length; i < length; i++) {
  // если текущий символ не равен а и о, то...
  if (str[i] !== 'а' && str[i] !== 'о') {
    // прекращаем выполнение текущей итерации и переходим к следующей
    continue;
  }
  // выведем в консоль сам символ и его индекс
  console.log(i + ' => ' + str[i]);
}
// данный цикл выведет в консоль: 2 => "о", 5 => "а", 10 => "о", 12 => "а"

Метки для break и continue

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

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

Далее после оператора break или continue необходимо указать эту метку:

someLabel: while (условие) {
  if (условие) {
    break someLabel;
  }
}

Вызов break someLabel приведёт к переходу в конец цикла, перед которым данная метка указана.

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

В коде с одиночным циклом использование метки не даст никакого результата. Её есть смысл использовать только когда вам нужно выйти сразу из нескольких циклов.

Пример, в котором выйдем сразу из 2 циклов, когда произведение значений переменных-счётчиков даст число большее 10.

// обозначим внешний цикл, используя метку outer
outer: for (var i = 2; i < 5; i++) {
  // вложенный цикл
  for (var j = 2; j < 5; j++) {
    // если условие выполняется, то прерываем работу и переходим к концу цикла с меткой outer
    if (i * j > 10) break outer;
    // выведем в консоль
    console.log(i + ' * ' + j + ' = ' + i * j);
  }
}
// в консоль будет выведено: 2 * 2 = 4, 2 * 3 = 6, 2 * 4 = 8, 3 * 2 = 6, 3 * 3 = 9

Кроме этого, операторы break и continue нельзя использовать в выражениях тернарных операторов.

Цикл for...of (новинка в ES6)

Цикл for...of появился в стандарте ES6. Предназначен он для перебора итерируемых объектов, т.е. объектов, в которых реализован метод Symbol.iterator. Этот метод ещё называют итератором. Именно его и использует цикл for...of для перебора объектов.

Метод Symbol.iterator имеется у String, Array, Map, Set, arguments, NodeList и других объектов.

Пример использование цикла for...of для посимвольного перебора строки:

// переменная, содержащаая строку
let str = 'Новый';
// посимвольный перебор строки
for (let char of str) {
  console.log(char);
}
// в консоль будет выведено: "Н", "о", "в", "ы", "й"

Пример использование цикла for...of для перебора коллекции DOM-элементов:

let elements = document.querySelectorAll('p');
for (let element of elements) {
  console.log(element);
}

Пример использование цикла for...of для перебора массива:

// массив
let superHeroes = ['Iron Man', 'Thor', 'Hulk'];
// перебор массива
for (let value of superHeroes) {
  console.log(value);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk"

Чем цикл for...of отличается от for...in?

Первое отличие цикла for...of от for...in заключается в том, что он может применяться только для итерируемым объектов, т.е. объектов, в которых реализован итератор (Symbol.iterator). Цикл for...in итератор не использует. Он предназначен для перебора любых объектов.

Второе отличие заключается в том, что цикл for...of перебирает объект так, как это определено в итераторе. Например, в Array итератор реализован так, что цикл for...of пройдёт только по значениям в массиве и не будет включать в перебор другие (не индексные) свойства. Цикл for...in организован по-другому, он перебирает все перечисляемые свойства (имена ключей) объекта, в том числе и наследуемые.

Рассмотрим эти отличия. Для этого возьмём предыдущий пример и добавим к нему пользовательское свойство, например, hero и установим ему значение 'Wasp'.

let superHeroes = ['Iron Man', 'Thor', 'Hulk'];
superHeroes.hero = 'Wasp';  

При использовании for...of он переберёт все значения этого массива:

// цикл for...of
for (let value of superHeroes) {
  console.log(value);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk"

При использовании for...in он переберёт все перечисляемые имена ключей этого объекта:

// цикл for...in
for (let key in superHeroes) {
  console.log(key);
}
// в консоль будет выведено: 0, 1, 2, "hero"

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

// цикл for...in
for (let key in superHeroes) {
  console.log(superHeroes[key]);
}
// в консоль будет выведено: "Iron Man", "Thor", "Hulk", "Wasp"

Самостоятельное создание итератора для объекта

Рассмотрим ещё один пример. В этом примере мы самостоятельно определим как должен итерироваться объект. Для этого создадим объект и определим ему итератор.

Создание итератора начинается с добавления к объекту специального метода. Этот метод необходимо спроектировать так, чтобы он возвращал значения последовательно (одно за другим). Название методу согласно стандарту необходимо определить с помощью символа Symbol.iterator. Итератор должен возвращать всего один метод next(). Этот метод в свою очередь тоже должен возвращать объект, состоящий из 2 свойств: value и done. Ключ done - булевый. Он определяет есть ли ещё значения в последовательности (false - да, true - нет). Ключ value должен содержать следующее значение последовательности.

let car = {
  color: 'black',
  brand: 'Ford',
  // создадим итератор, используя символ
  [Symbol.iterator]() {
    // получим имена перечисляемых свойств объекта
    const keys = Object.keys(this);
    // создадим переменную (текущий индекс последовательности)
    let index = 0;
    return {
      next() {
        let done = index >= keys.length;
        let value = done ? undefined : keys[index++];
        return {
          value,
          done
        }
      }
    }
  }
}

for (let key in car) {
  console.log(key + ' => ' + car[key]);
}
// в консоль будет выведено: color => "black", brand => "Ford"