Зацикливание кода - Изучение веб-разработки

Зацикливание кода
=================

* Назад
* Обзор: Building blocks
* Далее

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

| Требования: | Базовые значения компьютерной системы и базовое понимание HTML и CSS, JavaScript первые шаги. |
| --- | --- |
| Цель: | Понять как работают циклы в JavaScript. |

Циклы в коде
------------

Циклы являются важной концепцией в программировании. Их использование основано на повторении одного и того же, что в программировании называется **итерацией**.

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

![](/ru/docs/Learn/JavaScript/Building_blocks/Looping_code/loop_js-02-farm.png)

Цикл обычно составляет одну или несколько из следующих функций:

* **Счётчик**, который инициализируется с определённого значения — начальной точки цикла (На рисунке выше первый этап: "у меня нет еды (i have no food)")
* **Условие выхода** — критерий, при котором цикл останавливается, — обычно наступает, когда цикл достигает определённого значения. Это иллюстрируется выше словами "Достаточно ли у меня еды? (Do I have enough food?)". Предположим, фермеру нужно 10 порций еды, чтобы прокормить семью.
* **Итератор** постепенно увеличивает счётчик на некоторое значение на каждом шаге цикла, пока не достигнуто условия выхода. Мы явно не показали это в изображении, но если предположить что фермер собирает две порции еды в час, то после каждого часа, количество еды, которое у него имеется, увеличивается на две порции, и он проверяет достаточно ли у него еды сейчас. Если у него собралось 10 порций (условие выхода), он может остановить сбор и вернуться домой.

В псевдокоде это будет выглядеть следующим образом:
```

loop(food = 0; foodNeeded = 10) {
  if (food = foodNeeded) {
    exit loop;
    // У нас достаточно еды, пора домой
  } else {
    food += 2; // Прошёл час, количество еды увеличилось на 2
    // переход на следующую итерацию цикла.
  }
}
```

Таким образом, необходимое количество еды устанавливается равным 10, а изначально фермер не имеет ни одной порции, т.е. начало равно 0. На каждой итерации цикла проверяем, соответствует ли собранное количество еды, с тем количеством, которое ему необходимо. Если это так, можно выйти из цикла, если нет, фермер собирает ещё 2 порции и снова переходит к проверке.

### Зачем это нужно?

Итак вы разобрались, как работают циклы. Но, вероятно, думаете: "Хорошо, но как это мне поможет писать код на JavaScript". Как мы писали ранее, **циклы постоянно повторяют одно и тоже действие**, что отлично подходит для **быстрого выполнения повторяющихся задач**.

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

На следующем примере попробуем показать, почему циклы так полезны. Предположим мы хотели нарисовать 100 случайных кругов на элементе `<canvas>`. Нажмите кнопку "Обновить", чтобы снова и снова запускать пример и увидеть, что круги рисуются случайным образом.
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Random canvas circles</title>
    <style>
 html {
 width: 100%;
 height: inherit;
 background: #ddd;
 }

 canvas {
 display: block;
 }

 body {
 margin: 0;
 }

 button {
 position: absolute;
 top: 5px;
 left: 5px;
 }
 </style>
  </head>
  <body>
    <button>Обновить</button>

    <canvas></canvas>

    <script>
 var btn = document.querySelector("button");
 var canvas = document.querySelector("canvas");
 var ctx = canvas.getContext("2d");

 var WIDTH = document.documentElement.clientWidth;
 var HEIGHT = document.documentElement.clientHeight;

 canvas.width = WIDTH;
 canvas.height = HEIGHT;

 function random(number) {
 return Math.floor(Math.random() \* number);
 }

 function draw() {
 ctx.clearRect(0, 0, WIDTH, HEIGHT);
 for (var i = 0; i < 100; i++) {
 ctx.beginPath();
 ctx.fillStyle = "rgba(255,0,0,0.5)";
 ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 \* Math.PI);
 ctx.fill();
 }
 }

 btn.addEventListener("click", draw);
 </script>
  </body>
</html>
```

Вам необязательно понимать все части кода, но давайте посмотрим на место, где рисуются 100 кругов.

```js
for (var i = 0; i < 100; i++) {
  ctx.beginPath();
  ctx.fillStyle = "rgba(255,0,0,0.5)";
  ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 \* Math.PI);
  ctx.fill();
}
```

* `random()`, описанная в коде, возвращает случайное число между `0` и `x-1`.
* `WIDTH` и `HEIGHT` — это высота и ширина окна браузера.

Вы должны понять основную идею: мы используем цикл для запуска 100 итераций этого кода, каждая из которых рисует круг в случайном месте в окне. Количество кода было бы одинаковым, если бы нам нужно было нарисовать 10, 100 или 1000 кругов, поменяется лишь одно число.

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

```js
ctx.beginPath();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 \* Math.PI);
ctx.fill();
```

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

Правила записи цикла
--------------------

Рассмотрим некоторые конкретные конструкции циклов. Первый вариант, который вы будете использовать чаще всего, это цикл for. Он имеет следующий синтаксис:
```

for (initializer; exit-condition; final-expression) {
  // код для выполнения
}
```

Тут имеем:

1. Ключевое слово for, за которым следуют круглые скобки.
2. В круглых скобках у нас есть три части, разделённые точкой с запятой:
    1. **Инициализатор** — обычно это переменная численного типа, которая увеличивается каждую итерацию, чтобы посчитать количество шагов цикла. Её также называет **счётчиком**.
    2. **Условие выхода** — как упоминалось ранее, определяет, когда цикл должен остановиться. Обычно это выражение с оператором сравнения проверяющим, выполнено ли условие выхода.
    3. **Окончательное выражение** — вычисляется (или выполняется) каждый раз, когда цикл проходит полную итерацию. Обычно оно служит для увеличения (или уменьшения) переменной **счётчика**, чтобы приблизить её значение к условию выхода.
3. Фигурные скобки, содержащие блок кода. Этот код будет запускаться на каждой итерации цикла.

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

```js
var cats = ["Билл", "Макс", "Пикси", "Алиса", "Жасмин"];
var info = "Моих кошек зовут ";
var para = document.querySelector("p");

for (var i = 0; i < cats.length; i++) {
  info += cats[i] + ", ";
}

para.textContent = info;
```

Этот блок кода будет иметь следующий результат:
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Пример цикла For</title>
    <style></style>
  </head>
  <body>
    <p></p>

    <script>
 var cats = ["Билл", "Макс", "Пикси", "Алиса", "Жасмин"];
 var info = "Моих кошек зовут: ";
 var para = document.querySelector("p");

 for (var i = 0; i < cats.length; i++) {
 info += cats[i] + ", ";
 }

 para.textContent = info;
 </script>
  </body>
</html>
```

**Примечание:** вы можете найти этот пример на GitHub или посмотреть онлайн.

 Здесь показан цикл, используемый для перебора элементов в массиве и выполнения определённых действий с каждым из них — очень распространённый шаблон в JavaScript
 Подробнее:

1. Итератор, `i`, начинается с `0` (`var i = 0`).
2. Цикл запускается, пока значение итератора не будет больше длины массива кошек. Это важно - условие выхода показывает когда именно цикл должен работать, а когда нужно выйти из цикла. Поэтому в случае, пока `i < cats.lenght` по-прежнему возвращает `true`, цикл будет работать.
3. Внутри тела цикла мы соединяем текущий элемент цикла (`cats[i]` это `cats`[независимо от того, чем `i` является в данный момент]) с запятой и пробелом. Итак:
    1. В начале, `i = 0`, поэтому `cats[0] + ', '` соединятся в ("Билл, ").
    2. На втором шаге, `i = 1`, поэтому `cats[1] + ', '` соединятся в ("Макс, ")
    3. И так далее. В конце каждого цикла `i` увеличится на 1 (`i++`) , и процесс будет начинаться заново.
4. Когда `i` достигнет величины `cats.length` цикл остановится и браузер перейдёт к следующему фрагменту кода после цикла.

**Примечание:** Мы добавили условия выхода `i < cats.length`, а не `i <= cats.length`, потому что компьютеры считают с 0, а не с 1 — в начале `i = 0` и увеличивается до `i = 4` (индекс последнего элемента массива). `cats.length` возвращает 5, т.к. в массиве 5 элементов, но нам не нужно увеличивать до `i = 5`, т.к. `cats[5]` вернёт `undefined` (в массиве нет элемента с индексом 5). Таким образом мы хотим придти к результату на 1 меньше, поэтому `i < cats.length`, не одно и тоже что `i <= cats.length`.

**Примечание:** Стандартной ошибкой с условием выхода является использование условия "равный" (`===`) ,а не "меньше или равно" (`<=`). Если нам нужно увеличить счётчик до `i = 5`, условие выхода должно быть `i <= cats.length`. Если мы установим `i === cats.length`, цикл не начнётся, т.к. `i` не равно `5` на самой первой итерации, поэтому цикл остановится сразу.

Остаётся одна небольшая проблема: выходная строка сформирована не совсем корректно:

>
> Моих кошек зовут Билл, Макс, Пикси, Алиса, Жасмин,
>
>
>

В идеале мы хотим изменить конкатенацию на последней итерации цикла так, чтобы у нас не было запятой в конце предложения. Для этого нужно добавить условное выражение внутрь цикла `for` для обработки этого особого случая:

```js
for (var i = 0; i < cats.length; i++) {
  if (i === cats.length - 1) {
    info += "и " + cats[i] + ".";
  } else {
    info += cats[i] + ", ";
  }
}
```

**Примечание:** вы можете найти этот пример на GitHub или посмотреть онлайн.

**Предупреждение:** **Важно**: С циклом **for**, также как и с другими циклами, вы должны убедиться что инициализатор (счётчик) и окончательное выражение построены так, что они достигнут условия выхода. Если этого не произойдёт, то цикл будет продолжаться вечно. В итоге браузер или заставит его остановиться, или выдаст ошибку. Это называется **бесконечным циклом**.

Выход из цикла с помощью break
------------------------------

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

Тоже самое и с циклами — условие `break` моментально закончит цикл и заставит браузер запустить следующий после цикла код.

 Предположим, в массиве данных мы хотим найти имена контактов и телефонные номера, а вернуть только номер, который мы нашли.
 Начнём с разметки HTML: поле `<input>` позволяет нам ввести имя для поиска, элемент (кнопка) `<button>` для подтверждения поиска, и элемент `<p>` для отображения результата:

```html
<label for="search">Поиск по имени: </label>
<input id="search" type="text" />
<button>Поиск</button>

<p></p>
```

Теперь в JavaScript:

```js
var contacts = [
  "Григорий:2232322",
  "Марина:3453456",
  "Василий:7654322",
  "Наталья:9998769",
  "Диана:9384975",
];
var para = document.querySelector("p");
var input = document.querySelector("input");
var btn = document.querySelector("button");

btn.addEventListener("click", function () {
  var searchName = input.value;
  input.value = "";
  input.focus();
  for (var i = 0; i < contacts.length; i++) {
    var splitContact = contacts[i].split(":");
    if (splitContact[0] === searchName) {
      para.textContent = splitContact[0] + ", тел.: " + splitContact[1] + ".";
      break;
    } else {
      para.textContent = "Контакт не найден.";
    }
  }
});
```

```
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Simple contact search example</title>
    <style></style>
  </head>
  <body>
    <label for="search">Найти имя контакта: </label>
    <input id="search" type="text" />
    <button>Поиск</button>

    <p></p>

    <script>
 var contacts = [
 "Григорий:2232322",
 "Марина:3453456",
 "Василий:7654322",
 "Наталья:9998769",
 "Диана:9384975",
 ];
 var para = document.querySelector("p");
 var input = document.querySelector("input");
 var btn = document.querySelector("button");

 btn.addEventListener("click", function () {
 var searchName = input.value;
 input.value = "";
 input.focus();
 for (var i = 0; i < contacts.length; i++) {
 var splitContact = contacts[i].split(":");
 if (splitContact[0] === searchName) {
 para.textContent =
 splitContact[0] + ", тел.: " + splitContact[1] + ".";
 break;
 } else if (i === contacts.length - 1)
 para.textContent = "Контакт не найден.";
 }
 });
 </script>
  </body>
</html>
```

1. Прежде всего у нас определены некоторые переменные: у нас есть массив с контактной информацией, каждый элемент которого это строка, содержащая в себе имя и номер телефона, которые разделены двоеточием.
2. Далее мы применяем обработчик события кнопки (`btn`), чтобы при её нажатии запускался код для поиска и отображения результатов.
3. Мы сохраняем значение, введённое в текстовое поле, в переменную `searchName`, затем очищаем введённый текст и снова фокусируемся на текстовом поле для нового поиска.
4. Теперь перейдём к интересующей нас части — к циклу `for`:
    1. Мы начали счётчик с `0`, запускаем цикл до тех пор, пока счётчик всё ещё меньше, чем contacts.length, а инкремент `i` увеличиваем на 1 после каждой итерации цикла.
    2. Внутри цикла мы сначала разделяем текущий контакт (`contacts[i]`) на символе двоеточия, и сохраняем полученные два значения в массиве с названием `splitContact`.
    3. Затем мы используем условный оператор, чтобы проверить, равно ли `splitContact[0]` (имя контакта) введённому `searchName`. Если это так, мы выводим строку в абзац, чтобы сообщить, каков номер контакта, и используем `break` для завершения цикла.
5. После итерации `(contacts.length-1)`, если имя контакта не совпадает с введённым именем в поисковом запросе, для текста абзаца устанавливается: «Контакт не найден.», и цикл продолжает повторяться.

**Примечание:** Примечание: вы можете посмотреть исходный код на GitHub или запустить его (also see it running live).

Пропуск итераций с продолжением
-------------------------------

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

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

Код HTML в основном такой же, как и в предыдущем примере — простой ввод текста и абзац для вывода. JavaScript в основном такой же, хотя сам цикл немного другой:

```js
var num = input.value;

for (var i = 1; i <= num; i++) {
  var sqRoot = Math.sqrt(i);
  if (Math.floor(sqRoot) !== sqRoot) {
    continue;
  }

  para.textContent += i + " ";
}
```

Вывод:
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Генератор целочисленных квадратов</title>
    <style></style>
  </head>
  <body>
    <label for="number">Введите число: </label>
    <input id="number" type="text" />
    <button>Генерация целочисленных квадратов</button>

    <p>Результат:</p>

    <script>
 var para = document.querySelector("p");
 var input = document.querySelector("input");
 var btn = document.querySelector("button");

 btn.addEventListener("click", function () {
 var num = input.value;
 input.value = "";
 input.focus();
 para.textContent = "Вы ввели: " + num + ". Результат: ";
 for (var i = 1; i <= num; i++) {
 var sqRoot = Math.sqrt(i);
 if (Math.floor(sqRoot) !== sqRoot) {
 continue;
 }

 para.textContent += i + " ";
 }
 });
 </script>
  </body>
</html>
```

1. В этом случае на входе должно быть число (`num`). Циклу `for` присваивается счётчик, начинающийся с 1 (поскольку в данном случае нас не интересует 0), условие выхода, которое говорит, что цикл остановится, когда счётчик станет больше входного `num`, а итератор добавляет 1 к счётчику каждый раз.
2. Внутри цикла мы находим квадратный корень каждого числа с помощью Math.sqrt(i), а затем проверяем, является ли квадратный корень целым числом, проверяя, совпадает ли он с самим собой, когда он был округлён до ближайшего целого числа (это то, что `Math.floor ()` делает с числом, которое передаёт).
3. Если квадратный корень и округлённый вниз квадратный корень не равны друг другу `(! ==)`, значит квадратный корень не является целым числом, поэтому нас он не интересует. В таком случае мы используем оператор `continue`, чтобы перейти к следующей итерации цикла без записи этого числа.
4. Если квадратный корень *является целым числом*, мы пропускаем блок `if` полностью, поэтому оператор `continue` не выполняется; вместо этого объединяется текущее значение `i` с пробелом в конце содержимого абзаца.

**Примечание:** Вы также можете просмотреть полный исходный код на GitHub (так же смотри этот код вживую).

Циклы `while` и `do...while`
----------------------------

`for`— `не единственный тип цикла, доступный в JavaScript.` На самом деле существует множество других типов циклов. И хотя сейчас не обязательно знать их все, стоит взглянуть на структуру нескольких других, чтобы вы могли распознать те же функции, но в работе немного по-другому.

Рассмотрим цикл while. Синтаксис этого цикла выглядит так:
```

initializer
while (exit-condition) {
  // code to run

  final-expression
}
```

Его действие очень похоже на цикл `for`, но переменная инициализатора устанавливается перед циклом, а заключительное выражение включается в цикл после запуска кода. Напомним, у цикла `for` эти два элемента заключены в круглых скобках. Здесь же после ключевого слова `while в круглых скобках заключено условие выхода из цикла.`

Здесь присутствуют те же три элемента и в том же порядке, что и в цикле `for`. Это важно, так как вам нужно определить инициализатор, прежде чем получится проверить, достиг ли цикл условия выхода.

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

Посмотрим ещё раз пример со списком кошек с кодом переписанным под использование цикла `while:`

```js
var i = 0;

while (i < cats.length) {
  if (i === cats.length - 1) {
    info += "and " + cats[i] + ".";
  } else {
    info += cats[i] + ", ";
  }

  i++;
}
```

>
> \*\*Примечание:\*\*цикл все ещё работает так же, как и ожидалось - посмотрите, как он работает на GitHub (также посмотрите полный исходный код).
>
>
>

Цикл\*\* do...while\*\* представляет собой вариацию структуры цикла `while`:
```

initializer
do {
  // code to run

  final-expression
} while (exit-condition)
```

В этом случае инициализатор снова указывается прежде, чем цикл запускается. Ключевое слово `do` непосредственно предшествует фигурным скобкам, содержащим код для запуска и конечное выражение.

Различие состоит в том, что условие выхода идёт после всего остального, заключённое в скобки после ключевого слова `while`. В цикле `do...while` код внутри фигурных скобок всегда запускается один раз, прежде чем выполняется проверка, чтобы увидеть, должна ли она быть выполнена снова (в `while` и `for` проверка идёт первой, поэтому код может быть не выполнен).

Перепишем наш пример с кошками, чтобы использовать цикл `do...while`:

```js
var i = 0;

do {
  if (i === cats.length - 1) {
    info += "and " + cats[i] + ".";
  } else {
    info += cats[i] + ", ";
  }

  i++;
} while (i < cats.length);
```

>
> \*\*Примечание:\*\*И снова это работает так же, как и ожидалось - посмотрите, как он работает на GitHub (также посмотрите полный исходный код).
>
>
>

**Предупреждение:** **Замечание**: Применяя циклы `while` and `do...while` , как и все циклы, убедитесь, что инициализатор повторяется так, чтобы он в конце концов достиг условия выхода. Если это не так, цикл будет продолжаться вечно, и либо браузер заставит его остановиться, либо произойдёт сбой. Это называется *~~доктор Стрэндж и Дормамму~~* **бесконечным циклом**.

Практическое упражнение: запускаем обратный отсчёт!
---------------------------------------------------

В этом упражнении мы хотим, чтобы вы написали простой отсчёт времени запуска до поля вывода, от 10 до "Пуск!" В частности, мы хотим, чтобы вы:

* Цикл от 10 до 0. Мы предоставляем вам инициализатор: `var i = 10;`.
* Для каждой итерации создайте новый абзац и добавьте его к выходному элементу `<div>`, который мы выбрали, используя `var output = document.querySelector ('.output');`. В комментариях мы предоставили вам три строки кода, которые необходимо использовать где-то внутри цикла:
    + `var para = document.createElement('p');` — создать новый абзац.
    + `output.appendChild(para);` — добавляет абзац к выводу `<div>`.
    + `para.textContent =` — делает текст внутри абзаца равным значению, которое вы расположили справа, после знака равенства.
* Разные номера итераций требуют, чтобы в абзаце для каждой итерации помещался свой текст (вам понадобится условный оператор и несколько `para.textContent = lines`
 ):
    + Если число равно 10, выведите в абзаце «Обратный отсчёт 10».
    + Если число равно 0, выведите в абзаце «Пуск!»
    + Для любого другого числа выведите в абзаце только число.
* Не забудьте включить итератор! Однако в этом примере мы ведём обратный отсчёт после каждой итерации, а не вверх, поэтому вам **не** нужен `i ++`. Как выполнить итерацию вниз?

Если вы допустили ошибку, вы всегда можете сбросить пример с помощью кнопки «Сброс» (Reset). Если у вас совсем ничего не получается, нажмите «Show solution», чтобы увидеть решение.
```

<h2>Live output</h2>
<div class="output" style="height: 410px;overflow: auto;"></div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>
<textarea id="code" class="playable-code" style="height: 300px;width: 95%">
var output = document.querySelector('.output');
output.innerHTML = '';

// var i = 10;

// var para = document.createElement('p');
// para.textContent = ;
// output.appendChild(para);
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;
var userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

var jsSolution =
  "var output = document.querySelector('.output');\noutput.innerHTML = '';\n\nvar i = 10;\n\nwhile(i >= 0) {\n var para = document.createElement('p');\n if(i === 10) {\n para.textContent = 'Countdown ' + i;\n } else if(i === 0) {\n para.textContent = 'Blast off!';\n } else {\n para.textContent = i;\n }\n\n output.appendChild(para);\n\n i--;\n}";
var solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  var scrollPos = textarea.scrollTop;
  var caretPos = textarea.selectionStart;

  var front = textarea.value.substring(0, caretPos);
  var back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );
  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = function () {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

Практическое упражнение: Заполнение списка гостей
-------------------------------------------------

Возьмите список имён, хранящихся в массиве, и поместите их в список гостей. Тут не всё так просто: мы не хотим приглашать Фила и Лолу, потому что они наглые и всё сожрут! У нас есть два списка. Один для тех, кого мы хотим пригласить, второй для тех, кого приглашать не нужно.

Для этого нужно сделать следующее:

* Напишите цикл, который будет повторяться от 0 до длины массива `people`.
 Вам нужно начать с инициализатора `var i = 0;` , но какое нужно использовать условие выхода?
* Во время каждой итерации цикла нужно проверять, соответствует ли текущий элемент массива именам "Фил" или "Лола", используя условный оператор:
    + Если это так, привяжите этот элемент массива в конец `textContent` абзаца `refused`, за которым следуют запятая и пробел.
    + Если это не так, привяжите этот элемент массива в конец `textContent` абзаца `admitted`, за которым следуют запятая и пробел.

Мы уже предоставили вам:

* `var i = 0;` — ваш инициализатор.
* `refused.textContent +=` — начало строки, которая объединит что-то до конца `refused.textContent`.
* `admitted.textContent +=` — начало строки, которая объединит что-то до конца `admitted.textContent`.

Дополнительный бонусный вопрос - после успешного выполнения вышеупомянутых задач у вас останется два списка имён, разделённых запятыми, но они будут составлены некорректно: в конце каждого списка будет запятая. Сможете ли вы составить эти списки так, чтобы в конце каждой строки вместо запятой была точка. За помощью можно обратиться к статье «Полезные строковые методы».

Если вы допустили ошибку, вы всегда можете сбросить пример с помощью кнопки «Сброс» (Reset). Если у вас совсем ничего не получается, нажмите «Показать решение», чтобы увидеть решение.
```

<h2>Live output</h2>
<div class="output" style="height: 100px;overflow: auto;">
  <p class="admitted">Пригласить:</p>
  <p class="refused">Не приглашать(!):</p>
</div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>
<textarea id="code" class="playable-code" style="height: 400px;width: 95%">
var people = ['Крис', 'Анна', 'Колин', 'Терри', 'Фил', 'Лола', 'Сём', 'Кай', 'Брюс'];

var admitted = document.querySelector('.admitted');
var refused = document.querySelector('.refused');
admitted.textContent = 'Пригласить: ';
refused.textContent = 'Не приглашать(!): '

// var i = 0;

// refused.textContent += ;
// admitted.textContent += ;

</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Сброс" />
  <input id="solution" type="button" value="Показать решение" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;
var userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Показать решение";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Показать решение") {
    textarea.value = solutionEntry;
    solution.value = "Скрыть решение";
  } else {
    textarea.value = userEntry;
    solution.value = "Показать решение";
  }
  updateCode();
});

var jsSolution =
  "var people = ['Крис', 'Анна', 'Колин', 'Терри', 'Фил', 'Лола', 'Сём', 'Кай', 'Брюс'];\n\nvar admitted = document.querySelector('.admitted');\nvar refused = document.querySelector('.refused');\n\nadmitted.textContent = 'Пригласить: ';\nrefused.textContent = 'Не приглашать(!): '\nvar i = 0;\n\ndo {\n if(people[i] === 'Фил' || people[i] === 'Лола') {\n refused.textContent += people[i] + ', ';\n } else {\n admitted.textContent += people[i] + ', ';\n }\n i++;\n} while(i < people.length);\n\nrefused.textContent = refused.textContent.slice(0,refused.textContent.length-2) + '.';\nadmitted.textContent = admitted.textContent.slice(0,admitted.textContent.length-2) + '.';";
var solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  var scrollPos = textarea.scrollTop;
  var caretPos = textarea.selectionStart;

  var front = textarea.value.substring(0, caretPos);
  var back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );
  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = function () {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

Так какой тип цикла использовать?
---------------------------------

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

**`For`**:
```

for (initializer; exit-condition; final-expression) {
  // code to run
}
```

**`while`**:
```

initializer
while (exit-condition) {
  // code to run

  final-expression
}
```

**`do...while`**:
```

initializer
do {
  // code to run

  final-expression
} while (exit-condition)
```

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

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

Заключение
----------

Эта статья раскрывает основные концепции и различные опции, доступные при циклическом кодировании в JavaScript. Теперь вам должно быть понятно, почему циклы являются хорошим механизмом для работы с повторяющимся кодом. Старайтесь использовать их в своих собственных примерах!

Если вы чего-то не поняли, пробуйте читать статью снова или свяжитесь с нами, мы поможем разобраться.

Дополнительная информация
-------------------------

* Циклы и итерации
* for свойства и характеристики
* while и do...while описание
* break и continue описание
* What's the Best Way to Write a JavaScript For Loop? — статья о практическом применении циклов
* Назад
* Обзор: Building blocks
* Далее


Возвращаемые значения функций - Изучение веб-разработки

Возвращаемые значения функций
=============================

* Назад
* Обзор: Building blocks
* Далее

Для нас в этом курсе имеется ещё один важный момент. Посмотрим внимательнее на возвращаемое значение функций. Некоторые функции не возвращают существенное значение после завершения, но некоторые возвращают, и важно понимать что это за значение и как использовать его в своём коде и как сделать так чтобы ваши собственные функции возвращали полезные значения. Мы объясним всё это ниже.

| Необходимые навыки: | Базовая компьютерная грамотность, знание основ HTML и CSS, JavaScript first steps, Functions — reusable blocks of code. |
| --- | --- |
| Цели: | Понять что такое возвращаемое значение функции и как его использовать. |

Что из себя представляют возвращаемые значения?
-----------------------------------------------

**Возвращаемые значения** - это на самом деле просто значения, которые функция возвращает после своего завершения. Вы уже неоднократно встречали возвращаемые значения, хотя, возможно, и не осознавали этого. Напишем небольшой код:

```js
var myText = "I am a string";
var newString = myText.replace("string", "sausage");
console.log(newString);
// функция replace() принимает строку,
// заменяет одну подстроку другой и возвращает
// новую строку со сделанными заменами
```

Мы уже видели этот блок кода в нашей первой статье про функции. Мы вызываем функцию replace() на строке `myText` и передаём ей 2 параметра — заменяемую подстроку и подстроку, которой будем заменять. Когда функция завершит выполнение, она вернёт значение, которым является новая строка со сделанными в ней заменами. В коде выше мы сохраняем это возвращаемое значение как значение переменной `newString`.

Если вы посмотрите на функцию replace() на MDN reference page, вы увидите секцию под названием Return value. Очень важно знать и понимать какие значения возвращаются функциями, так что мы пытаемся включать эту информацию везде, где это возможно.

Некоторые функции не возвращают значения( на наших reference pages, возвращаемое значение обозначено как `void` или `undefined` в таких случаях). Например, в функции displayMessage() которую мы сделали в прошлой статье, в результате выполнения функции не возвращается никакого значения. Функция всего лишь отображает что-то где-то на экране.

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

### Использование возвращаемых значений в ваших собственных функциях

Чтобы вернуть значение своей функции, вы должны использовать ключевое слово return. Мы видели это в действии недавно - в нашем примере random-canvas-circles.html. Наша функция `draw()` отрисовывает где-то на экране 100 случайных кружков.

`<canvas>`:

```js
function draw() {
  ctx.clearRect(0, 0, WIDTH, HEIGHT);
  for (var i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = "rgba(255,0,0,0.5)";
    ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 \* Math.PI);
    ctx.fill();
  }
}
```

Внутри каждой итерации есть 3 вызова функции `random()`. Это сделано чтобы сгенерировать случайное значение для текущей координаты x, y и для радиуса. Функция `random()` принимает 1 параметр (целое число) и возвращает случайное число в диапазоне от 0 до этого числа. Выглядит это вот так:

```js
function random(number) {
  return Math.floor(Math.random() \* number);
}
```

Тоже самое может быть написано вот так:

```js
function random(number) {
  var result = Math.floor(Math.random() \* number);
  return result;
}
```

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

Мы возвращаем результат вычисления `Math.floor(Math.random()*number)` каждый раз когда функция вызывается. Это возвращаемое значение появляется в момент вызова функции и код продолжается. Так, например, если мы выполним следующую строчку:

```js
ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 \* Math.PI);
```

и 3 вызова `random()` вернут значения 500, 200 и 35, соответственно, строчка будет выполнена как если бы она была такой:

```js
ctx.arc(500, 200, 35, 0, 2 \* Math.PI);
```

Сначала выполняются вызовы функции `random()`, на место которых подставляются возвращаемые ей значения, а затем выполнятся сама строка.

Активное обучение: наша собственная, возвращающая значение функция
------------------------------------------------------------------

Теперь напишем нашу собственную возвращающую значение функцию.

1. Первым делом, сделайте локальную копию файла function-library.html из GitHub. Это простая HTML страничка, содержащая текстовое поле `<input>` и параграф Также там есть элемент `<script>` в котором мы храним в 2ух переменных ссылки на оба HTML-элемента. Это маленькая страничка позволит вам ввести число в text box и отобразит различные, относящиеся к нему числа в параграфе ниже.
2. Теперь добавим несколько полезных функций в элемент `<script>` . Ниже двух существующих строчек JavaScript, добавьте следующие описания функций:

```js
function squared(num) {
  return num \* num;
}

function cubed(num) {
  return num \* num \* num;
}

function factorial(num) {
  var x = num;
  while (x > 1) {
    num \*= x - 1;
    x--;
  }
  return num;
}
```

`Ф` функции `squared()` и `cubed()` довольно очевидны— они возвращают квадрат или куб переданного как параметр числа. Функция `factorial()` возвращает factorial переданного числа.
3. Далее мы добавим способ выводить нашу информацию введённым в text input числе. Добавьте обработчик событий ниже существующих функций:

```js
input.onchange = function () {
  var num = input.value;
  if (isNaN(num)) {
    para.textContent = "You need to enter a number!";
  } else {
    para.textContent =
      num +
      " squared is " +
      squared(num) +
      ". " +
      num +
      " cubed is " +
      cubed(num) +
      ". " +
      num +
      " factorial is " +
      factorial(num) +
      ".";
  }
};
```

Здесь мы создаём обработчик событий `onchange` который срабатывает когда меняется когда новое значение вводится в text input и подтверждается (введите значение и, например, нажмите tab). Когда анонимная функция срабатывает, введённое в input значение сохраняется в переменной `num` .
4. Далее мы делаем условный тест — если введённое значение не является числом, мы выводим в параграф сообщение об ошибке. Тест смотрит возвращает ли выражение `isNaN(num)` true. Мы используем функцию isNaN() чтобы проверить что значение переменной num не число — если так то функция возвращает `true`, если нет- `false`.
5. Если тест возвращает `false`, значение переменной `num` число, и поэтому мы выводим сообщение внутри параграфа о значениях квадрата, куба и факториала числа. Предложение вызывает функции `squared()`, `cubed()` и `factorial()` чтобы получить нужные значения. Сохраните ваш код, загрузите его в браузере и посмотрите на то что получилось.

**Примечание:** Если у вас проблемы с работой данного примера, не стесняйтесь сверить ваш код с работающей версией finished version on GitHub (или смотрите живой пример), или спросите нас.

К этому моменту мы хотели бы чтобы вы написали парочку собственных функций и добавили их в библиотеку. Как на счёт квадратного или кубического корня числа или длины окружности круга с длиной радиуса равной числу `num`?

Это упражнение привнесло парочку важных понятий в изучении того, как использовать ключевое слово `return` . В дополнение:

* Приведите другой пример написание обработчика ошибок. Это довольно хорошая идея проверять что важные параметры предоставлены в правильном типе и если они опциональны то предусматривать для них значения по умолчанию. В таком случая ваша программа с меньшей вероятность подвержена ошибкам.
* Поразмышляйте о идее создания библиотеки функций. Чем дальше вы будите расти в профессиональном плане, тем больше будете сталкиваться с однотипными вещами. Это хорошая идея начать собирать свою собственную библиотеку функций, которые вы часто используют — в таком случае вы сможете просто скопировать их в ваш новый код или просто добавить их в любую HTML страничку, где это требуется.
Заключение
----------

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

Если в статье есть что-то что вы не поняли, не стесняйтесь перечитать статью ещё раз или свяжитесь с нами для получения помощи.

Смотрите также
--------------

* Функции более подробно — подробное руководство, охватывающее более продвинутую информацию, связанную с функциями.
* Колбэк-функции в JavaScript — распространённый паттерн JavaScript для передачи функции в другую функцию как аргумент, который затем вызывается внутри первой функции.
* Назад
* Обзор: Building blocks
* Далее
In this module
--------------

* Making decisions in your code — conditionals
* Looping code
* Functions — reusable blocks of code
* Build your own function
* Function return values
* Introduction to events
* Image gallery


Проверь свои навыки: Функции - Изучение веб-разработки

Проверь свои навыки: Функции
============================

Целью данного теста навыков является оценка понимания таких статей, как: Функций — многократное использование блоков кода, Построение своих собственных функций и Возвращаемые значения функции.

**Примечание:** Примечание: вы можете проверить решения в интерактивных редакторах ниже, однако может быть полезно загрузить код и использовать онлайн-инструменты, такие как CodePen, jsFiddle или Glitch для работы над задачами.

Если у вас возникла проблема, то попросите нас о помощи - см. раздел Анализ или дальнейшая помощь внизу этой страницы.

**Примечание:** Примечание: В примерах ниже, если в вашем коде есть ошибка, она будет выведена на панель результатов этой страницы, чтобы помочь вам попытаться выяснить ответ (или в JavaScript консоли браузера, в случае загружаемой версии).

Управление структурой DOM: рекомендуется
----------------------------------------

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

Мы ещё не обучали этому прямо в курсе, но вы увидите некоторые примеры, которые используют данную структуру, и мы хотели бы, чтобы вы провели некоторые исследования в отношении того, какие DOM API вам нужны, чтобы успешно ответить на эти вопросы. Хорошим началом будет наше учебное пособие Управление документами.

Функции 1
---------

Для решения первой задачи вам нужно создать простую функцию — `chooseName()`, которая выполнит выборку случайного имени из данного массива (`names`) и выведет его в предоставленный параграф (`para`). А затем необходимо запустить эту функцию один раз.

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

**Примечание:** Загрузите файл с исходным кодом для этой задачи для работы в собственном редакторе или в онлайн-редакторе.

Функции 2
---------

Для решения второй задачи, связанной с функциями, необходимо создать функцию, которая рисует прямоугольник на заданном элементе `<canvas>` (исходная переменная `canvas`, контекст доступен в `ctx`), основанном на пяти предоставленных входящих значениях:

* `x` — x координата прямоугольника.
* `y` — y координата прямоугольника.
* `width` — ширина прямоугольника.
* `height` — высота прямоугольника.
* `color` — цвет прямоугольника.

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

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

**Примечание:** Загрузите файл с исходным кодом для этой задачи для работы в собственном редакторе или в онлайн-редакторе.

Функции 3
---------

В этой задаче вы возвращаетесь к проблеме, поставленной в Задаче 1, с целью её улучшения. Мы хотим, чтобы вы сделали три улучшения:

1. Выполните рефакторинг (реорганизацию) кода, который отвечает за генерацию случайного числа в отдельную функцию `random()`, которая принимает в качестве параметров две общие границы, между которыми должно находиться случайное число и возвращает результат.
2. Обновите функцию `chooseName()` таким образом, чтобы она использовала функцию случайных чисел, принимала массив для выбора в качестве параметра (что делает его более гибким) и возвращала результат.
3. Вывести возвращаемый результат в параграф (`para`) с помощью свойства `textContent`.

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

**Примечание:** Загрузите файл с исходным кодом для этой задачи для работы в собственном редакторе или в онлайн-редакторе.

Анализ или дальнейшая помощь
----------------------------

Вы можете попрактиковаться на этих примерах в интерактивных редакторах выше.

Если же вы хотите, чтобы вашу работу проанализировали, или у вас проблема и вы хотите попросить о помощи:

1. Поместите свой код в онлайн-редактор CodePen, jsFiddle или Glitch с возможностью совместного использования. Вы можете написать код самостоятельно или использовать файлы с исходным кодом, ссылки на которые приведены в вышеприведённых разделах.
2. Напишите сообщение с просьбой о проведении анализа и/или помощи в категории MDN Discourse forum Learning category. Ваше сообщение должно включать в себя следующие пункты:
    * Описательный заголовок, например "Анализ для теста навыков: Функции 1".
    * Детали того, что вы уже пробовали, и что бы вы хотели, чтобы мы сделали, например, если вы застряли и нуждаетесь в помощи, или хотите получить анализ проблемы.
    * Ссылка на пример, который вы хотели бы рассмотреть или с которым вам нужна помощь, в онлайн-редакторе, доступном для совместного использования (как упоминалось в шаге 1 выше). Это хорошая практика - очень сложно помочь кому-то с проблемой, если вы не видите его код.
    * Ссылка на фактическую задачу или страницу рассмотрения проблемы, чтобы мы могли найти вопрос, по которому вам нужна помощь.


Галерея изображений - Изучение веб-разработки

Галерея изображений
===================

* Назад
* Обзор: Building blocks

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

| Необходимые знания: | Перед выполнением этого задания вы должны проработать все статьи в этом модуле. |
| --- | --- |
| Цель: | Проверить понимание циклов, функций, условных операторов и событий в JavaScript. |

Отправная точка
---------------

Для начала скачайте ZIP файл для примера и распакуйте его содержимое у себя на компьютере.

**Примечание:** вы также можете использовать такие сайты как JSBin или Thimble для выполнения задания. Вы можете скопировать HTML, CSS и JavaScript в один из этих редакторов. Если онлайн редактор, который вы выбрали, не имеет отдельных панелей для JavaScript/CSS, вы можете выставить код в теги `<script>`/`<style>` расположенные на самой HTML странице.

Обзор проекта
-------------

В примере вы увидите несколько файлов HTML, CSS и изображений и несколько строк JavaScript-кода. Вам нужно написать недостающий JavaScript-код для того чтобы галерея заработала. Каркас HTML страницы выглядит так:

```html
<h1>Пример фотогалереи</h1>

<div class="full-img">
  <img class="displayed-img" src="images/pic1.jpg" />
  <div class="overlay"></div>
  <button class="dark">Темнее</button>
</div>

<div class="thumb-bar"></div>
```

Результат выглядит следующим образом:

![](https://github.com/ConstantineZz/javaScript/blob/master/gallery.png?raw=true)

Наиболее интересные части CSS файла из примера:

* Абсолютно позиционированы три элемента внутри `<div class="full-img">`: `<img>`, в котором отображается полноразмерное изображение, пустой `<div class="overlay">`, размер которого имеет тот же размер, что и `<img>` помещается прямо над предыдущим div-ом (это используется для нанесения эффекта затемнения на изображение через полупрозрачный цвет фона) и `<button>`, который используется для управления эффектом затемнения.
* Задана ширина любых изображений внутри `<div class="thumb-bar">` (так называемые «уменьшенные изображения») до 20% и размещены слева так, чтобы они следовали друг за другом на линии.

Ваш JavaScript должен:

* Переберите все изображения, и для каждого вставьте элемент `<img>` внутри `thumb-bar <div>`, который будет вставлять это изображение на страницу.
* Прикрепите обработчик `onclick` к каждому `<img>` внутри `thumb-bar <div>`, чтобы при нажатии на них соответствующее изображение отображалось в элементе `display-img <img>`.
* Прикрепите обработчик `onclick` к кнопке `<button>`, чтобы при нажатии на неё к полноразмерному изображению был применён эффект затемнения. При повторном нажатии эффект затемнения снова удаляется.

Чтобы лучше понять идею, посмотрите на готовый пример (не заглядывая в исходный код!).

Этапы выполнения
----------------

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

### Зацикливание изображений

В файле main.js уже предоставлены строки, в которых хранится ссылка на `thumb-bar <div>` внутри переменной с именем `thumbBar`, создают новый элемент `<img>`, устанавливают его атрибут `src` на значение placeholder `xxx` и добавляют этот новый `<img>` элемент внутри `thumbBar`.

Нужно сделать:

1. Поместите раздел кода под комментарием `/* Looping through images */` внутри цикла, который перебирает все 5 изображений - вам просто нужно перебрать пять чисел, каждое из которых представляет каждое изображение.
2. В каждой итерации цикла замените значение-заполнитель `xxx` строкой, которая будет равна пути к изображению в каждом случае. Мы устанавливаем значение атрибута `src` для этого значения в каждом случае. Имейте в виду, что в каждом случае изображение находится внутри каталога изображений, а его имя - `pic1.jpg`, `pic2.jpg` и т.д.
### Добавление обработчика onclick к каждому уменьшенному изображению

В каждой итерации цикла вам нужно добавить обработчик `onclick` к текущему `newImage`:

1. Найдите значение атрибута `src` текущего изображения. Это можно сделать, запустив функцию `getAttribute()` в `<img>` в каждом случае и передав ей параметр `«src»` в каждом случае. Но как получить изображение? Использование `newImage` не будет работать, так как цикл завершается до применения обработчиков событий; это приведёт к тому, что значение `src` последнего `<img>` будет возвращено в каждом случае. Чтобы решить это, имейте в виду, что в случае каждого обработчика события `<img>` является целью обработчика. Как получить информацию от объекта события?
2. Запустите функцию, передав ей возвращаемое значение `src` в качестве параметра. Вы можете вызвать эту функцию, как хотите.
3. Эта функция обработчика событий должна установить значение атрибута `src` `displayed-img <img>` равным значению `src`, переданному в качестве параметра. Мы уже предоставили вам строку, в которой хранится ссылка на соответствующий `<img>` в переменной с именем `displayedImg`. Обратите внимание, что здесь нам нужна определённая именованная функция.
### Написание обработчика, который запускает кнопку затемнения / подсветки

Мы уже предоставили строку, в которой хранится ссылка на `<button>` в переменной `btn`. Вам нужно добавить обработчик `onclick`, который:

1. Проверяет текущее имя класса, установленное на кнопке `<button>` — для этого снова можно использовать `getAttribute()`.
2. Если имя класса `"dark"`, изменяет класс `<button>` на `"light"` (с помощью `setAttribute()`), его текстовое содержимое на "Светлее" и `background-color` наложения `<div>` на `"rgba (0,0,0,0.5)"`.
3. Если имя класса не «тёмное», изменяет класс `<button>` на `"dark"`, его текстовое содержимое обратно на "Темнее" и `background-color` наложения `<div>` на `"rgba(0,0,0,0)"`.

Следующие строки служат основой для достижения изменений, указанных в пунктах 2 и 3 выше.

```js
btn.setAttribute("class", xxx);
btn.textContent = xxx;
overlay.style.backgroundColor = xxx;
```

Советы и подсказки
------------------

* Вам не нужно каким-либо образом редактировать HTML или CSS.
Оценка
------

Если вы проводите эту оценку в рамках организованного курса, вы должны уметь отдать свою работу своему учителю/наставнику для маркировки. Если вы самообучаетесь, то вы можете получить руководство по маркировке довольно легко, задав тему обсуждения об этом упражнении или в IRC-канале #mdn в Mozilla IRC. Сначала попробуйте упражнение - ничего не выиграть от обмана!

* Назад
* Обзор: Building blocks


Введение в события - Изучение веб-разработки

Введение в события
==================

* Назад
* Обзор: Building blocks
* Далее

События — это действия или случаи, возникающие в программируемой вами системе, о которых система сообщает вам для того, чтобы вы могли с ними взаимодействовать. Например, если пользователь нажимает кнопку на веб-странице, вы можете ответить на это действие, отобразив информационное окно. В этой статье мы обсудим некоторые важные концепции, связанные с событиями, и посмотрим, как они работают в браузерах. Эта статья не является исчерпывающим источником по этой теме — здесь только то, что вам нужно знать на этом этапе.

| Предпосылки: | Базовая компьютерная грамотность, базовое понимание HTML и CSS, Первые шаги в JavaScript. |
| --- | --- |
| Задача: | Понять фундаментальную теорию событий, как они работают в браузерах и как события могут различаться в разных средах программирования. |

Серия удачных событий
---------------------

При возникновении **события** система генерирует сигнал, а также предоставляет механизм, с помощью которого можно автоматически предпринимать какие-либо действия (например, выполнить определённый код), когда происходит событие. Например, в аэропорту, когда взлётно-посадочная полоса свободна для взлёта самолёта, сигнал передаётся пилоту, и в результате они приступают к взлёту.

![](/ru/docs/Learn/JavaScript/Building_blocks/Events/mdn-mozilla-events-runway.png)

В Web события запускаются внутри окна браузера и, как правило, прикрепляются к конкретному элементу, который в нем находится. Это может быть один элемент, набор элементов, документ HTML, загруженный на текущей вкладке, или все окно браузера. Существует множество различных видов событий, которые могут произойти, например:

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

Подробнее о событиях можно посмотреть в Справочнике по событиям.

Каждое доступное событие имеет **обработчик событий** — блок кода (обычно это функция JavaScript, вводимая вами в качестве разработчика), который будет запускаться при срабатывании события. Когда такой блок кода определён на запуск в ответ на возникновение события, мы говорим, что мы **регистрируем обработчик событий**. Обратите внимание, что обработчики событий иногда называют слушателями событий (от англ. event listeners). Они в значительной степени взаимозаменяемы для наших целей, хотя, строго говоря, они работают вместе. Слушатель отслеживает событие, а обработчик — это код, который запускается в ответ на событие.

**Примечание:** Важно отметить, что веб-события не являются частью основного языка JavaScript. Они определены как часть JavaScript-API, встроенных в браузер.

### Простой пример

Рассмотрим простой пример. Вы уже видели события и обработчики событий во многих примерах в этом курсе, но давайте повторим для закрепления информации. В этом примере у нас есть кнопка `<button>`, при нажатии которой цвет фона изменяется случайным образом:

```html
<button>Change color</button>
```

```
button {
  margin: 10px;
}
```

JavaScript выглядит так:

```js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() \* (number + 1));
}

btn.onclick = function () {
  const rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
};
```

В этом коде мы сохраняем ссылку на кнопку внутри переменной `btn` типа `const`, используя функцию `Document.querySelector()`. Мы также определяем функцию, которая возвращает случайное число. Третья часть кода — обработчик события. Переменная `btn` указывает на элемент `<button>` — для этого типа объекта существуют возникающие при определённом взаимодействии с ним события, а значит, возможно использование обработчиков событий. Мы отслеживаем момент возникновения события при щелчке мышью, связывая свойство обработчика события `onclick` с анонимной функцией, генерирующей случайный цвет RGB и устанавливающей его в качестве цвета фона элемента `<body>`.

Этот код теперь будет запускаться всякий раз, когда возникает событие при нажатии на элемент `<button>` — всякий раз, когда пользователь щёлкает по нему.

Пример вывода выглядит следующим образом:

### События не только для веб-страниц

События, как понятие, относятся не только к JavaScript — большинство языков программирования имеют модель событий, способ работы которой часто отличается от модели в JavaScript. Фактически, даже модель событий в JavaScript для веб-страниц отличается от модели событий для просто JavaScript, поскольку используются они в разных средах.

Например, Node.js — очень популярная среда исполнения JavaScript, которая позволяет разработчикам использовать JavaScript для создания сетевых и серверных приложений. Модель событий Node.js основана на том, что существуют обработчики, отслеживающие события, и эмиттеры (передатчики), которые периодически генерируют события. В общем-то, это похоже на модель событий в JavaScript для веб-страниц, но код совсем другой. В этой модели используется функция `on()` для регистрации обработчиков событий, и функция `once()` для регистрации обработчика событий, который отключается после первого срабатывания. Хорошим примером использования являются протоколы событий HTTP connect event docs.

Вы также можете использовать JavaScript для создания кросс-браузерных расширений — улучшения функциональности браузера с помощью технологии WebExtensions. В отличии от модели веб-событий здесь свойства обработчиков событий пишутся в так называемом регистре CamelCase (например, `onMessage`, а не `onmessage`) и должны сочетаться с функцией `addListener`. См. runtime.onMessage page для примера.

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

Способы использования веб-событий
---------------------------------

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

### Свойства обработчика событий

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

```js
const btn = document.querySelector("button");

btn.onclick = function () {
  var rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
};
```

В данной ситуации свойство `onclick` — это свойство обработчика события. В принципе это обычное свойство кнопки как элемента (наравне с `btn.textContent` или `btn.style` (en-US)), но оно относится к особому типу. Если вы установите его равным какому-нибудь коду, этот код будет запущен при возникновении события (при нажатии на кнопку).

Для получения того же результата, вы также можете присвоить свойству обработчика имя уже описанной функции (как мы видели в статье Создайте свою функцию):

```js
const btn = document.querySelector("button");

function bgChange() {
  const rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;
```

Давайте теперь поэкспериментируем с другими свойствами обработчика событий.

Создайте локальную копию файла random-color-eventhandlerproperty.html и откройте её в своём браузере. Это всего лишь копия простого примера про случайные цвета, который мы уже разобрали в этой статье. Теперь попробуйте изменить `btn.onclick` на следующие значения и понаблюдайте за результатами:

* `btn.onfocus` и `btn.onblur` — Цвет изменится, когда кнопка будет сфокусирована или не сфокусирована (попробуйте нажать Tab, чтобы выбрать кнопку или убрать выбор). Эти свойства часто применяются для отображения информации о том, как заполнить поля формы, когда они сфокусированы, или отобразить сообщение об ошибке, если поле формы было заполнено с неправильным значением.
* `btn.ondblclick` (en-US) — Цвет будет изменяться только при двойном щелчке.
* `window.onkeypress` (en-US), `window.onkeydown` (en-US), `window.onkeyup` (en-US) — Цвет будет меняться при нажатии клавиши на клавиатуре, причём `keypress` ссылается на обычное нажатие (нажатие кнопки и последующее её отпускание *как одно целое*), в то время как `keydown` и `keyup` *разделяют* действия на нажатие клавиши и отпускание, и ссылаются на них соответственно. Обратите внимание, что это не работает, если вы попытаетесь зарегистрировать этот обработчик событий на самой кнопке - его нужно зарегистрировать на объекте window, который представляет все окно браузера.
* `btn.onmouseover` (en-US) и `btn.onmouseout` (en-US) — Цвет будет меняться при наведении указателя мыши на кнопку или когда указатель будет отводиться от кнопки соответственно.

Некоторые события очень общие и доступны практически в любом месте (например, обработчик `onclick` может быть зарегистрирован практически для любого элемента), тогда как некоторые из них более конкретны и полезны только в определённых ситуациях (например, имеет смысл использовать onplay (en-US) только для определённых элементов, таких как `<video>`).

### Встроенные обработчики событий - не используйте их

Самый ранний из введённых в сеть Web методов регистрации *обработчиков событий* базируется на **HTML атрибутах** (**встроенные обработчики событий**):

```html
<button onclick="bgChange()">Press me</button>
```

```js
function bgChange() {
  const rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
}
```

**Примечание:** вы можете найти полный исходник кода из этого примера на GitHub (также взгляните на его выполнение).

Значение атрибута — это буквально код JavaScript, который вы хотите запустить при возникновении события. В приведённом выше примере вызывается функция, определённая внутри элемента `<script>` на той же странице, но вы также можете вставить JavaScript непосредственно внутри атрибута, например:

```html
<button onclick="alert('Hello, this is my old-fashioned event handler!');">
  Press me
</button>
```

Для многих свойств обработчика событий существуют эквиваленты в виде атрибутов HTML. Однако, не рекомендуется их использовать — это считается плохой практикой. Использование атрибутов для регистрации обработчика событий кажется простым и быстрым методом, но такое описание обработчиков также скоро становится неудобным и неэффективным.

Более того, не рекомендуется смешивать HTML и JavaScript файлы, так как в дальнейшем такой код становится сложнее с точки зрения обработки (парсинга). Лучше держать весь JavaScript в одном месте. Также, если он находится в отдельном файле, вы можете применить его к нескольким документам HTML.

Даже при работе только в одном файле использование встроенных обработчиков не является хорошей идеей. Ладно, если у вас одна кнопка, но что, если у вас их 100? Вам нужно добавить в файл 100 атрибутов; обслуживание такого кода очень быстро превратится в кошмар. С помощью JavaScript вы можете легко добавить функцию обработчика событий ко всем кнопкам на странице независимо от того, сколько их было.

Например:

```js
const buttons = document.querySelectorAll("button");

for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}
```

Обратите внимание, что для перебора всех элементов, которые содержит объект `NodeList`, можно воспользоваться встроенным методом `forEach()` (en-US):

```js
buttons.forEach(function (button) {
  button.onclick = bgChange;
});
```

**Примечание:** Разделение логики вашей программы и вашего контента также делает ваш сайт более дружественным к поисковым системам.

### Функции addEventListener() и removeEventListener()

Новый тип механизма событий определён в спецификации Document Object Model (DOM) Level 2 Events, которая предоставляет браузеру новую функцию — `addEventListener()`. Работает она аналогично свойствам обработчика событий, но синтаксис совсем другой. Наш пример со случайным цветом мог бы выглядеть и так:

```js
var btn = document.querySelector("button");

function bgChange() {
  var rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
}

btn.addEventListener("click", bgChange);
```

**Примечание:** вы можете найти исходный код из этого примера на GitHub (также взгляните на его выполнение).

Внутри функции `addEventListener()` мы указываем два параметра — имя события, для которого мы хотим зарегистрировать этот обработчик, и код, содержащий функцию обработчика, которую мы хотим запустить в ответ. Обратите внимание, что будет целесообразно поместить весь код внутри функции `addEventListener()` в анонимную функцию, например:

```js
btn.addEventListener("click", function () {
  var rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
});
```

Этот механизм имеет некоторые преимущества по сравнению с более старыми механизмами, рассмотренными ранее. Например, существует аналогичная функция `removeEventListener()`, которая удаляет ранее добавленный обработчик. Это приведёт к удалению набора обработчиков в первом блоке кода в этом разделе:

```js
btn.removeEventListener("click", bgChange);
```

Это не важно для простых небольших программ, но для более крупных и более сложных программ он может повысить эффективность очистки старых неиспользуемых обработчиков событий. Кроме того, это позволяет вам иметь одну и ту же кнопку, выполняющую различные действия в разных обстоятельствах — все, что вам нужно сделать, это добавить/удалить обработчики событий, если это необходимо.

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

```js
myElement.onclick = functionA;
myElement.onclick = functionB;
```

Поскольку вторая строка будет перезаписывать значение `onclick`, установленное первой. Однако, если:

```js
myElement.addEventListener("click", functionA);
myElement.addEventListener("click", functionB);
```

Обе функции будут выполняться при щелчке элемента.

Кроме того, в этом механизме событий имеется ряд других мощных функций и опций. Эта тема выходит за рамки данной статьи, но если вы хотите изучить подробнее, переходите по ссылкам: Метод EventTarget.addEventListener() и Метод EventTarget.removeEventListener().

### Какой механизм мне использовать?

Из трёх механизмов определённо не нужно использовать атрибуты событий HTML. Как упоминалось выше, это устаревшая и плохая практика.

Остальные два являются относительно взаимозаменяемыми, по крайней мере для простых целей

* Свойства обработчика событий имеют меньшую мощность и параметры, но лучше совместимость между браузерами (поддерживается ещё в Internet Explorer 8). Вероятно, вам следует начать с них, когда вы учитесь.
* События уровня 2 DOM (`addEventListener()` и т. д.) являются более мощными, но также могут стать более сложными и хуже поддерживаться (поддерживается ещё в Internet Explorer 9). Вам также стоит поэкспериментировать с ними и стремиться использовать их там, где это возможно.

Основные преимущества третьего механизма заключаются в том, что при необходимости можно удалить код обработчика событий, используя `removeEventListener()`, и так же можно добавить несколько элементов-обработчиков того же типа к элементам. Например, вы можете вызвать `addEventListener('click', function() {...})` для элемента несколько раз, с разными функциями, указанными во втором аргументе. Это невозможно при использовании свойств обработчика событий, поскольку любые последующие попытки установить свойство будут перезаписывать более ранние, например:

```js
element.onclick = function1;
element.onclick = function2;
etc.
```

**Примечание:** Если вам требуется поддержка браузеров старше Internet Explorer 8 в вашем проекте, вы можете столкнуться с трудностями, так как такие старые браузеры используют старые модели событий из более новых браузеров. Но не бойтесь, большинство библиотек JavaScript (например, `jQuery`) имеют встроенные функции, которые абстрагируют различия между браузерами. Не беспокойтесь об этом слишком много на этапе вашего учебного путешествия.

Другие концепции событий
------------------------

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

### Объекты событий

Иногда внутри функции обработчика событий вы можете увидеть параметр, заданный с таким именем, как `event`, `evt` или просто `e`. Называется он **объектом события** и он автоматически передаётся обработчикам событий для предоставления дополнительных функций и информации. Например, давайте немного перепишем наш пример со случайным цветом:

```js
function bgChange(e) {
  var rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  e.target.style.backgroundColor = rndCol;
  console.log(e);
}

btn.addEventListener("click", bgChange);
```

**Примечание:** вы можете найти исходник кода для этого примера на GitHub (также взгляните на его выполнение).

Итак в коде выше мы включаем объект события **`e`** в функцию, а в функции — настройку стиля фона для `e.target`, который является кнопкой. Свойство объекта события `target` всегда является ссылкой на элемент, с которым только что произошло событие. Поэтому в этом примере мы устанавливаем случайный цвет фона на кнопке, а не на странице.

**Примечание:** Вместо `e`/`evt`/`event` можно использовать любое имя для объекта события, которое затем можно использовать для ссылки на него в функции обработчика событий. `e`/`evt`/`event` чаще всего используются разработчиками, потому что они короткие и легко запоминаются. И хорошо придерживаться стандарта.

`e.target` применяют, когда нужно установить один и тот же обработчик событий на несколько элементов и, когда на них происходит событие, применить определённое действие к ним ко всем. Например, у вас может быть набор из 16 плиток, которые исчезают при нажатии. Полезно всегда иметь возможность просто указать, чтобы объект исчез, как `e.target`, вместо того, чтобы выбирать его более сложным способом. В следующем примере (см. исходный код на useful-eventtarget.html,а как он работает можно посмотреть здесь), мы создаём 16 элементов `<div>` с использованием JavaScript. Затем мы выберем все из них, используя `document.querySelectorAll()`, и с помощью цикла `for` выберем каждый из них, добавив обработчик `onclick` к каждому так, чтобы случайный цвет применялся к каждому клику:

```js
var divs = document.querySelectorAll("div");

for (var i = 0; i < divs.length; i++) {
  divs[i].onclick = function (e) {
    e.target.style.backgroundColor = bgChange();
  };
}
```

Результат выглядит следующим образом (попробуйте щёлкнуть по нему):
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Useful event target example</title>
    <style>
 div {
 background-color: #ff6600;
 height: 100px;
 width: 25%;
 float: left;
 }
 </style>
  </head>
  <body>
    <script>
 for (var i = 1; i <= 16; i++) {
 var myDiv = document.createElement("div");
 document.body.appendChild(myDiv);
 }

 function random(number) {
 return Math.floor(Math.random() \* number);
 }

 function bgChange() {
 var rndCol =
 "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
 return rndCol;
 }

 var divs = document.querySelectorAll("div");

 for (var i = 0; i < divs.length; i++) {
 divs[i].onclick = function (e) {
 e.target.style.backgroundColor = bgChange();
 };
 }
 </script>
  </body>
</html>
```

Большинство обработчиков событий, с которыми вы столкнулись, имеют только стандартный набор свойств и функций (методов), доступных для объекта события (см. `Event` для ссылки на полный список). Однако некоторые более продвинутые обработчики добавляют специальные свойства, содержащие дополнительные данные, которые им необходимо выполнять. Например, Media Recorder API (en-US) имеет событие, доступное для данных, которое срабатывает, когда записано какое-либо аудио или видео и доступно для выполнения чего-либо (например, для сохранения или воспроизведения). Соответствующий объект события ondataavailable handler имеет свойство данных, содержащее записанные аудио- или видеоданные, чтобы вы могли получить к нему доступ и что-то сделать с ним.

### Предотвращение поведения по умолчанию

Иногда бывают ситуации, когда нужно остановить событие, выполняющее то, что оно делает по умолчанию. Наиболее распространённым примером является веб-форма, например, пользовательская форма регистрации. Когда вы вводите данные и нажимаете кнопку отправки, естественное поведение заключается в том, что данные должны быть отправлены на указанную страницу на сервере для обработки, а браузер перенаправляется на страницу с сообщением об успехе (или остаться на той же странице, если другое не указано).

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

Простая форма HTML, в которой требуется ввести ваше имя и фамилию:

```html
<form>
  <div>
    <label for="fname">Имя: </label>
    <input id="fname" type="text" />
  </div>
  <div>
    <label for="lname">Фамилия: </label>
    <input id="lname" type="text" />
  </div>
  <div>
    <input id="submit" type="submit" />
  </div>
</form>
<p></p>
```

```
div {
  margin-bottom: 10px;
}
```

В JavaScript мы реализуем очень простую проверку внутри обработчика события onsubmit (событие "отправить" запускается в форме, когда оно отправлено), который проверяет, пусты ли текстовые поля. Если они пусты, мы вызываем функцию `preventDefault()` объекта события, которая останавливает отправку формы, а затем выводит сообщение об ошибке в абзаце ниже нашей формы, чтобы сообщить пользователю, что не так:

```js
var form = document.querySelector("form");
var fname = document.getElementById("fname");
var lname = document.getElementById("lname");
var submit = document.getElementById("submit");
var para = document.querySelector("p");

form.onsubmit = function (e) {
  if (fname.value === "" || lname.value === "") {
    e.preventDefault();
    para.textContent = "Оба поля должны быть заполнены!";
  }
};
```

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

**Примечание:** чтобы увидеть исходный код, откройте preventdefault-validation.html (также запустите здесь).

### Всплытие и перехват событий

Последним предметом для рассмотрения в этой теме является то, с чем вы не часто будете сталкиваться, но это может стать настоящей головной болью, если вы не поймёте, как работает следующий механизм. *Всплытие* и *перехват событий* — два механизма, описывающих, что происходит, когда два обработчика одного и того же события активируются на одном элементе. Посмотрим на пример. Чтобы сделать это проще — откройте пример show-video-box.html в одной вкладке и исходный код в другой вкладке. Он также представлен ниже:
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Show video box example</title>
    <style>
 div {
 position: absolute;
 top: 50%;
 transform: translate(-50%, -50%);
 width: 550px;
 height: 420px;
 border-radius: 10px;
 background-color: #eee;
 background-image: linear-gradient(
 to bottom,
 rgba(0, 0, 0, 0.1),
 rgba(0, 0, 0, 0.4)
 );
 }

 .hidden {
 left: -50%;
 }

 .showing {
 left: 50%;
 }

 div video {
 display: block;
 width: 400px;
 margin: 50px auto;
 }
 </style>
  </head>
  <body>
    <button>Display video</button>

    <div class="hidden">
      <video>
        <source
 src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.mp4"
 type="video/mp4" />
        <source
 src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.webm"
 type="video/webm" />
        <p>
          Your browser doesn't support HTML5 video. Here is a
          <a href="rabbit320.mp4">link to the video</a> instead.
        </p>
      </video>
    </div>

    <script>
 var btn = document.querySelector("button");
 var videoBox = document.querySelector("div");
 var video = document.querySelector("video");

 btn.onclick = function () {
 displayVideo();
 };

 function displayVideo() {
 if (videoBox.getAttribute("class") === "hidden") {
 videoBox.setAttribute("class", "showing");
 }
 }

 videoBox.addEventListener("click", function () {
 videoBox.setAttribute("class", "hidden");
 });

 video.addEventListener("click", function () {
 video.play();
 });
 </script>
  </body>
</html>
```

Это довольно простой пример, который показывает и скрывает `<div>` с элементом `<video>` внутри него:

```html
<button>Display video</button>

<div class="hidden">
  <video>
    <source src="rabbit320.mp4" type="video/mp4" />
    <source src="rabbit320.webm" type="video/webm" />
    <p>
      Your browser doesn't support HTML5 video. Here is a
      <a href="rabbit320.mp4">link to the video</a> instead.
    </p>
  </video>
</div>
```

При нажатии на кнопку `<button>`, изменяется атрибут класса элемента `<div>` с `hidden` на `showing` (CSS примера содержит эти два класса, которые размещают блок вне экрана и на экране соответственно):

```css
div {
        position: absolute;
        top: 50%;
        transform: translate(-50%,-50%);
        ...
      }
.hidden {
   left: -50%;
  }
.showing {
   left: 50%;
  }
```

```js
var btn = document.querySelector("button");
btn.onclick = function () {
  videoBox.setAttribute("class", "showing");
};
```

Затем мы добавляем ещё пару обработчиков событий `onclick.` Первый к `<div>`, а второй к `<video>`. Идея заключается в том, чтобы при щелчке по области `<div>` вне зоны видео поле снова скрылось, а при клике в области `<video>` видео начало воспроизводиться.

```js
var videoBox = document.querySelector("div");
var video = document.querySelector("video");

videoBox.onclick = function () {
  videoBox.setAttribute("class", "hidden");
};

video.onclick = function () {
  video.play();
};
```

Но есть проблема: когда вы нажимаете на видео, оно начинает воспроизводиться, но одновременно вызывает скрытие `<div>`. Это связано с тем, что видео находится внутри `<div>,` это часть его, поэтому нажатие на видео фактически запускает оба вышеуказанных обработчика событий.

#### Всплытие и перехват событий — концепция выполнения

Когда событие инициируется элементом, который имеет родительские элементы (например, `<video>` в нашем случае), современные браузеры выполняют две разные фазы — фазу **захвата** и фазу **всплытия**.

На стадии **захвата** происходит следующее:

* Браузер проверяет, имеет ли самый внешний элемент (`<html>`) обработчик события `onclick`, зарегистрированный на нем на этапе захвата и запускает его, если это так.
* Затем он переходит к следующему элементу внутри `<html>` и выполняет то же самое, затем следующее и так далее, пока не достигнет элемента, на который на самом деле нажали.

На стадии **всплытия** происходит полная противоположность:

* Браузер проверяет, имеет ли элемент, который был фактически нажат, обработчик события `onclick`, зарегистрированный на нем в фазе всплытия, и запускает его, если это так.
* Затем он переходит к следующему непосредственному родительскому элементу и выполняет то же самое, затем следующее и так далее, пока не достигнет элемента `<html>`.

![](/ru/docs/Learn/JavaScript/Building_blocks/Events/bubbling-capturing.png)

(Нажмите на изображение, чтобы увеличить диаграмму)

В современных браузерах по умолчанию все обработчики событий регистрируются в фазе ***всплытия***. Итак, в нашем текущем примере, когда вы нажимаете видео, событие click вызывается из элемента `<video>` наружу, в элемент `<html>`. По пути:

* Он находит обработчик `video.onclick...` и запускает его, поэтому видео сначала начинает воспроизводиться.
* Затем он находит обработчик `videoBox.onclick...` и запускает его, поэтому видео также скрывается.

#### Исправление проблемы с помощью stopPropagation()

Чтобы исправить это раздражающее поведение, стандартный объект события имеет функцию, называемую `stopPropagation()`, которая при вызове в обработчике событий объекта делает так, чтобы обработчик выполнялся, но событие не всплывало дальше по цепочке, поэтому не будут запускаться другие обработчики.

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

```js
video.onclick = function (e) {
  e.stopPropagation();
  video.play();
};
```

Вы можете попробовать создать локальную копию show-video-box.html и попробовать его самостоятельно исправить или просмотреть исправленный результат в show-video-box-fixed.html (также см. исходный код здесь).

**Примечание:** Зачем беспокоиться как с захватом, так и с всплыванием? Что ж, в старые добрые времена, когда браузеры были менее совместимы, чем сейчас, Netscape использовал только захват событий, а Internet Explorer использовал только всплывающие события. Когда W3C решил попытаться стандартизировать поведение и достичь консенсуса, они в итоге получили эту систему, которая включала в себя и то, и другое, что реализовано в одном из современных браузеров.

**Примечание:** Как упоминалось выше, по умолчанию все обработчики событий регистрируются в фазе всплытия и это имеет смысл в большинстве случаев. Если вы действительно хотите зарегистрировать событие в фазе захвата, вы можете сделать это, зарегистрировав обработчик с помощью `addEventListener()` и установив для третьего дополнительного свойства значение `true`.

#### **Делегирование** события

Всплытие также позволяет нам использовать **делегирование событий.** Если у какого-либо родительского элемента есть множество дочерних элементов, и вы хотите, чтобы определённый код выполнялся при щелчке (событии) на каждом из дочерних элементов, можно установить обработчик событий на родительском элементе и события, происходящие на дочерних элементах будут всплывать до их родителя. При этом не нужно устанавливать обработчик событий на каждом дочернем элементе.

Хорошим примером является серия элементов списка. Если вы хотите, чтобы каждый из них, например, отображал сообщение при нажатии, вы можете установить обработчик событий `click` для родительского элемента `<ul>` и он будет всплывать в элементах списка.

Эту концепцию объясняет в своём блоге Дэвид Уолш, где приводит несколько примеров. (см. How JavaScript Event Delegation Works.)

Вывод
-----

Это все, что вам нужно знать о веб-событиях на этом этапе. Как уже упоминалось, события не являются частью основного JavaScript — они определены в веб-интерфейсах браузера (Web API).

Кроме того, важно понимать, что различные контексты, в которых используется JavaScript, обычно имеют разные модели событий — от веб-API до других областей, таких как браузерные расширения и Node.js (серверный JavaScript). Может сейчас вы не особо в этом разбираетесь, но по мере изучения веб-разработки начнёт приходить более ясное понимание тематики.

Если у вас возникли вопросы, попробуйте прочесть статью снова или обратитесь за помощью к нам.

Смотрите также
--------------

* Event order (обсуждение захвата и всплытий) — превосходно детализированная часть от Peter-Paul Koch.
* Event accessing (discussing of the event object) — another excellently detailed piece by Peter-Paul Koch.
* Event reference
* Назад
* Обзор: Building blocks
* Далее