JS для начинающих. Урок 1.11: Циклы

javascript-cycleПри написании скриптов часто возникает потребность выполнять какое-то действие несколько раз. Для того, чтобы в этом случае не дублировать код существуют операторы циклов. Их мы и рассмотрим в этом уроке.

Оператор while

Синтаксис этого оператора такой:

while (условие) {
  // код
}

Код внутри фигурных скобок будет выполняться, пока выражение в круглых скобках после while будет равно true. Естественно, что на значение этого выражения может повлиять код в фигурных скобках. Одно выполнение кода в фигурных скобках называется шагом цикла или итерацией.

Например, посчитаем десять кроликов:

var rabbits = 0;
 
while (rabbits < 10) {
  rabbits++;
  console.log(rabbits + ' кролик(ов)');  
}

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

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

var rabbits = 0;
 
while (rabbits++ < 10) {
    console.log(rabbits + ' кролик(ов)');  
}

Оператор do-while

Синтаксис этого оператора такой:

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

Отличается он от оператора while тем, что условие проверяется после выполнения кода в фигурных скобках, а не до, как в операторе while, поэтому код в фигурных скобках выполнится всегда хотя бы один раз. Например:

var a = false;
 
do {
  console.log(a); // выполнится один раз
} while(a);

Оператор for

Оператор цикла for используется намного чаще, чем операторы while и do-while, поскольку во многих случаях он более удобен так как имеет некоторую переменную-счётчик, при проверки которой и выясняется, будет ли продолжать работать цикл или нет. В нашем случае переменная-счётчик это количество посчитанных кроликов.

Синтаксис этого оператора такой:

for (инициализация; проверка; изменение) {
  // code
}

Выражение «инициализация» выполняется один раз перед началом работы цикла. Обычно в этом выражении размещают инициализацию переменной-счётчика. При использовании цикла while это необходимо сделать до начала работы самого оператора. Следующее выражение «проверка» это тоже самое что и условие в круглых скобках, после ключевого слова while. Пока это выражение равно true, цикл продолжает работать. Последнее выражение «изменение» изменяет переменную-счётчик. При использовании цикла while мы, как правило, вынуждены это делать в блоке кода в фигурных скобках (в теле цикла).

Теперь посчитаем кроликов используя оператор цикла for:

for (var rabbits = 0; rabbits < 10; rabbits++) {
  console.log(rabbits + ' кролик(ов)'); 
}

Выражение «инициализация» может содержать инициализацию нескольких переменных, а выражение «изменение» несколько инструкций. К примеру, такой код выведет пары двух одинаковых чисел от нуля до четырёх.

for (var i = 0, j = 0; i + j < 10; i++, j++) {
  console.log(i, j);
}

Операторы break и continue

С оператором break вы уже познакомились, когда читали про операторы ветвления кода. Он приводит к немедленному выходу из цикла или из оператора switch. Например:

for (var i = 0; i < 10; i++) {
  if (i == 7) {
    break;
  }
}
console.log(i); // выведет 7

Оператор continue вместо выхода из цикла запускает его новую итерацию. Причём для разных операторов цикла это происходит по-разному. В цикле while и do-while условие после прерывания итерации проверяется снова и, если оно истинно, то тело цикла выполняется сначала. Этот оператор допустимо использовать только в циклах. Использование его в другом месте приведёт к ошибке.
К примеру код:

var a = 0;
 
while (a < 5) {
  a++;
  if (a == 2) {
    continue;
  }
  console.log(a);
}

выведет в столбик числа 1, 3, 4, 5. Без двойки, поскольку, когда a будет равно двум, тело цикла начнёт выполняться сначала, а будет увеличено до трёх и двойка не будет выведена. Поскольку условие продолжения работы цикла проверяется снова при выполнении оператора continue, то код:

1
2
3
4
5
6
7
8
9
10
var a = 0;
 
while (a < 5) {
  a++;
  if (a == 2) {
    a = 5;
    continue;
  }
  document.write(a);
}

выведет только единичку на чистой страничке. Здесь я отказался от использования своей моей любимой консоли, чтобы не запутать вас, поскольку если заменить document.write на console.log и ввести этот код с консоли, то можно увидеть числа 1 и 5 на разных строчках. Не знаю почему, но на консоль выводится значение а, которое было ему присвоено перед оператором continue.

В цикле for оператор continue прервёт выполнение итерации, затем произойдёт выполнение выражения «изменение» (третье по счёту выражение в круглых скобках после for) и, если условие продолжения цикла выполнено, то выполнится следующая итерация. К примеру код:

for (var i = 0; i < 5; i++) {
  if (i == 2) {
    continue;
  }
  console.log(i);
}

выведет в столбик числа 0, 1, 3, 4 поскольку когда i будет равно двум, тело цикла начнёт выполняться сначала и перед этим значение i будет инкрементировано.

Вложенные циклы

Операторы циклов, как и операторы ветвления кода, можно вкладывать друг в друга. Классическим примером работы таких циклов является вывод таблицы умножения.

for (var i = 2; i<= 10; i++) {
  for (var j = 2; j <= 10; j++) {
    document.write(i + '*' + j + ' = ' + i * j + '<br />');
  }
  document.write('<br />');
}

Демо

Теперь попытаемся понять, как это работает. Первый (внешний) цикл увеличивает значение первого множителя в произведении, а второй (внутренний) второго. За одну итерацию внешнего цикла внутренний делает девять. В его теле происходит умножении фиксированного i на меняющеюся j и вывод получившегося значения на страницу. Как только значение j достигает 11 выполнение внутреннего цикла прекращается, мы отступаем одну строчку и внешний цикл начинает новую итерацию при этом первый множитель (i) на единицу увеличивается. Как только значение i достигает 11 выполнение внешнего цикла, а значит и внутреннего цикла прекращается.

Ещё один пример, который иллюстрирует работу вложенных циклов:

for (var i = 1; i < 10; i++) {
  for (var j = 1; j <= i; j++) {
    document.write(i + " ");
  }
  document.write('<br />');
}

Демо

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

1
1 2
1 2 3

или в таком:

9
9 8
9 8 7
9 8 7 6

9 8 7 6 5 4 3 2 1

Если вы честно попытались, но у вас не получилось — в архиве с примерами есть решение этой задачи.

Использование меток с операторами break и continue

Существует форма оператора break c меткой. Именем метки может служить любой уникальный идентификатор. Метка может именовать любой оператор switch, for, if или даже просто блок кода в фигурных скобках. При использовании оператора break с меткой выход происходит не из текущего блока кода, а из того блока кода, который именует метка. Метка должна именовать внешний по отношении к ключевому слову break блок кода. Её использование имеет смысл только во вложенных циклах или операторах ветвления кода, так как код:

label:
for (var i = 0; i < 10; i++) {
  if (i == 7) {
    break label;
  }
}
console.log(i); // выведет 7

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

outer:
for (var i = 0; i < 4; i++) {
  for (var j = 0; j < 4; j++) {
    if (i == 2) {
      break outer;
    }
    console.log(i, j);
  }
}

Результатом выполнения этого кода будет вывод на консоль пар чисел 0, 0; 0, 1; 0, 2; 0, 3; 1, 0; 1, 1; 1, 2; 1, 3. Пары, где первая двойка, не будут выведены, поскольку при i равном 2 произойдёт выход из внешнего цикла и внутренний цикл завершит работу вместе со внешним.

Оператор continue c меткой действует точно так же как и без метки, за исключением того, что происходит прерывание итерации именованного цикла, а не цикла в котором он находится.

outer:
for (var i = 0; i < 4; i++) {
  for (var j = 0; j <2; j++) {
    if (i == 2) {
      continue outer;
    }
    console.log(i, j);
  }
}

Этот код выведет на консоль пары чисел 0, 0; 0, 1; 1, 0; 1, 1; 3, 0; 3, 1. Пары, где первая двойка будут пропущены, поскольку при i равном двум итерация внешнего цикла прервётся, значение i будет инкрементировано и выполнения тела внешнего цикла начнётся заново.

На этом всё. И, как всегда, я желаю вам успехов.

Архив с иходниками

  • Тарас

    Здравствуйте Антон. Статья та что мне нужна, спасибо — только на практике ничего не получается, не могу соединить все вмести. Скажите пожалуйста, как должен выглядеть код. У меня 10 карт, но должны переворачивается только 5. Скрывать я уже научился, просто не умею написать функцию с массива.

    • Уточните, пожалуйста, условия своей задачи. Каким образом вы описываете такую сущность, как карта? Какие пять карт? Первые, последние, случайные? Что значит «перевернуть карту» и «скрыть карту»? Какие действия над элементами массива соответствуют этим операциям?

      Мне будет намного понятнее, если вы приведёте код вашего пробного решения.

  • Тарас

    В начале “Вложенные циклы” вы пробел не поставили for (var здесь пробел i = 2; i<= 10; i++) {

  • Тарас

    Не будем лесть в дебри, интересует меня как сделать, чтобы цифры были рамдомными и не повторялись
    У меня повторяются – к сожалению

    var ar = 0;
    while (ar++ < 10) {
      document.write(Math.round(Math.random() * 10));
    }
    • В вашем случае можно использовать массив, для хранения случайных чисел и добавлять в него новое случайное число только в случае, если оно ещё туда не было добавлено.

      /*
       * Функция isContains служит для проверки вхождения элемента element в массив array.
       * Возвращает true, если массив содержит элемент element,
       * и false в противном случае.
       *
       * Современные браузеры поддерживают метод indexOf,
       * который принимает в качестве параметра элемент и 
       * возвращает его позицию в массиве или -1, если массив
       * не содержит такого элемента.
       * 
       * Если вам не нужна поддержка старых браузеров (например iе8 и ниже),
       * то можете использовать более короткий закомментированный вариант функции isContains.
       * Только не забудьте при этом удалить или закометировать старый вариант этой функции,
       * иначе будет ошибка.
       */
       
      /* function isContains(array, element) {
        return array.indexOf(element) != -1;
      } */
       
      function isContains(array, element) {
        var i = 0;
       
        // Если массив пуст, то он, очевидно, не содержит никаких элементов
        if (array.length == 0) {
          return false;
        }
       
        // перебираем все элементы массива
        for ( ; i < array.length; i++) {
          // и если находим элемент, равный искомому, то возвращаем true
          if (array[i] === element) {
            return true;
          }
        }
       
        // если таких элементов не было найдено возвращаем false
        return false;
      }
       
      /*
       * Функция для получения случайного целого числа из отрезка [min; max]
       * (включая значения min и max
       */
       
      function getRandomInt(min, max) {
       /*
        * использование Math.round даёт неравномерное расспределение,
        * то есть, если использовать Math.round(), то случайные числа
        * по краям отрезка [min; max] будут встречаться реже, чем в его центре.
        * для небольшого количества случайных чисел это не существенно, но я решил
        * использовать более верную реализацию.
        */
        return min + Math.floor((max - min + 1) * Math.random());
      }
       
      /*
       * функция для получения массива длины length,
       * заполненого уникальными (не повторяющимися) элементами,
       * принимающими случайные значения на отрезке [min; max]
       */
       
      function getUniqueRandomArray(min, max, length) {
        // массив, который будет заполняться уникальными случайными значениями
        var array = [],
        i = 0, // количество добавленных уникальных элементов в массив
        rnd; // случайное значение
       
        while (i < length) {
          // получаем случайное значение
          rnd = getRandomInt(min, max);
          // и если его нет в массиве
          if (!isContains(array, rnd)) {
            array.push(rnd); // то добавляем его
            ++i; // и увеличиваем количество добавленных элементов на единицу
          }
        }
       
        return array;
      }
       
      // получаем массив длины 10 со случайными числами из отрезка [0, 10]
      var randomArray = getUniqueRandomArray(0, 10, 10),
        // склеиваем элементы массива в строку, где они будут разделены запятой и пробелом
        result = randomArray.join(', ');
       
      /*
       * выводим, получившуюся строку в документ
       * стоит заметить, что использование document.write
       * не желательно, но для учебного примера сойдёт.
       */
      document.write(result);
  • Тарас

    Спасибо, начну разбираться. А если на jquery, код будет такой же или покороче?

    • При помощи jQuery разве что функцию isContains можно переписать как-то так

      function isContains(array, element) {
        return jQuery.inArray(element, array) != -1;
      }

      и это будет полностью кроссбраузерным вариантом.

  • Тарас

    Я никак не могу понять, как и куда нужно складывать картинки

    • Опять же, уточните что вы хотите с этими картинками делать. Как вы хотите отображать их на странице? При помощи тега img? Или вы хотите создавать его динамически? В любом случае картинки можно ложить в абсолютно любое место, откуда они будут доступны и потом указать путь к ним.

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