Вложенные циклы: цикл внутри цикла
Иногда задача требует перебора по двум измерениям одновременно: строки и столбцы таблицы, пары элементов, клетки игровой карты. Для этого существуют вложенные циклы — один цикл внутри другого. Это мощный инструмент, но с ним нужно обращаться осторожно: итерации перемножаются, и неосторожно написанный код может работать очень медленно.
🧩 Идея: два измерения — два цикла
Представьте, что вам нужно обойти все клетки шахматной доски 3×3. С одним циклом мы можем обойти один ряд. А чтобы обойти все ряды — нам нужен ещё один цикл снаружи.
Аналогия из жизни: вы проверяете все карманы в куртке. Куртка — внешний цикл (каждый карман). Внутри каждого кармана — внутренний цикл (достаёте каждую вещь). Сначала полностью обшариваете первый карман, потом второй, и так далее.
// Простейший вложенный цикл
for (let i = 1; i <= 3; i++) { // внешний: строки
for (let j = 1; j <= 3; j++) { // внутренний: столбцы
console.log(`(${i}, ${j})`);
}
}
// Результат:
// (1, 1) (1, 2) (1, 3)
// (2, 1) (2, 2) (2, 3)
// (3, 1) (3, 2) (3, 3)
Разберём пошагово:
- Внешний цикл устанавливает
i = 1 - Внутренний пробегает
jот 1 до 3 полностью: (1,1), (1,2), (1,3) - Внешний переходит к
i = 2 - Внутренний снова пробегает
jот 1 до 3: (2,1), (2,2), (2,3) - И так для каждого значения
i
🔑 Ключевое правило: при каждой итерации внешнего цикла внутренний цикл выполняется полностью — от начала до конца.
🧮 Сколько итераций? Умножаем!
Вот очень важная вещь, которую нужно понять про вложенные циклы: количество итераций перемножается.
10
итераций
100
итераций
1000
итераций
1 000 000
итераций — уже медленно!
Вот почему важно понимать, что происходит внутри вложенных циклов. Для маленьких N (десятки, сотни) это незаметно. Но когда N растёт — скорость падает квадратично: в два раза больше данных → в четыре раза дольше работает.
✖️ Классика: таблица умножения
Самый классический пример вложенных циклов — таблица умножения. Строки — один цикл, столбцы — другой:
// Таблица умножения от 1 до 5
for (let i = 1; i <= 5; i++) {
let row = "";
for (let j = 1; j <= 5; j++) {
// padStart выравнивает число до 4 символов
row += String(i * j).padStart(4, " ");
}
console.log(row);
}
// Результат:
// 1 2 3 4 5
// 2 4 6 8 10
// 3 6 9 12 15
// 4 8 12 16 20
// 5 10 15 20 25
Разбор: для каждой строки (значения i) мы собираем строку row, пробегая по всем столбцам (значениям j). Метод padStart(4, " ") добавляет пробелы в начало, чтобы числа выровнялись.
🗂️ Матрица: двумерный массив
В JavaScript матрица — это массив массивов. Каждый элемент внешнего массива — это строка (тоже массив). Обращаться к элементу: matrix[строка][столбец].
// Матрица 3×3
const matrix = [
[1, 2, 3], // строка 0
[4, 5, 6], // строка 1
[7, 8, 9] // строка 2
];
// Как обратиться к элементу:
console.log(matrix[0][0]); // 1 (строка 0, столбец 0)
console.log(matrix[1][2]); // 6 (строка 1, столбец 2)
console.log(matrix[2][1]); // 8 (строка 2, столбец 1)
Чтобы обойти все элементы матрицы, нужны вложенные циклы — внешний по строкам, внутренний по столбцам:
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
let sum = 0;
for (let i = 0; i < matrix.length; i++) { // перебор строк
for (let j = 0; j < matrix[i].length; j++) { // перебор столбцов
sum += matrix[i][j];
}
}
console.log("Сумма всех элементов:", sum); // 45
matrix.length — количество строк. matrix[i].length — количество столбцов в строке i (это важно: строки могут быть разной длины в JavaScript, хотя в математических матрицах обычно одинаковы).
Давайте создадим матрицу программно — заполним её так, что элемент [i][j] равен произведению i * j:
const n = 4;
const matrix = [];
for (let i = 0; i < n; i++) {
matrix[i] = []; // создаём пустую строку
for (let j = 0; j < n; j++) {
matrix[i][j] = i * j;
}
}
// matrix выглядит так:
// [[0, 0, 0, 0],
// [0, 1, 2, 3],
// [0, 2, 4, 6],
// [0, 3, 6, 9]]
⚡ Почему вложенные циклы могут быть медленными
Представьте: у вас есть список из 1000 студентов. Вам нужно найти все пары студентов с одинаковым именем. Наивное решение — сравнить каждого с каждым:
// Поиск дубликатов: O(n²) — медленный способ
for (let i = 0; i < students.length; i++) {
for (let j = i + 1; j < students.length; j++) { // j начинается с i+1
if (students[i].name === students[j].name) {
console.log("Совпадение:", students[i].name);
}
}
}
// При 1000 студентов: ~500 000 сравнений!
Это работает, но медленно. Если список вырастет до 10 000 студентов — будет 50 миллионов сравнений. Компьютер справится, но начнёт заметно тормозить.
💡 O(n²) — квадратичная сложность. Количество операций растёт как квадрат от размера данных:
- Данных в 10 раз больше → операций в 100 раз больше
- Данных в 100 раз больше → операций в 10 000 раз больше
Для больших данных часто есть более умные алгоритмы (например, сортировка + один цикл). Но для новичков главное — осознавать, что вложенные циклы умножают итерации, и не использовать их там, где достаточно одного.
- Обход матрицы N×M
- Таблица умножения
- Рисование сетки/паттерна
- Небольшие данные (N < 1000)
- Сравнение каждого с каждым
- Большие массивы (тысячи эл-тов)
- Три уровня вложенности
- Видимые тормоза в браузере
🛑 break во вложенных циклах: только из внутреннего!
Вот важная вещь, которая удивляет многих новичков: break во вложенных циклах выходит только из внутреннего цикла, а не из всей конструкции.
for (let i = 1; i <= 3; i++) {
console.log("Внешний:", i);
for (let j = 1; j <= 5; j++) {
if (j === 3) break; // выходим только из внутреннего!
console.log(" Внутренний:", j);
}
}
// Результат:
// Внешний: 1
// Внутренний: 1
// Внутренний: 2
// Внешний: 2
// Внутренний: 1
// Внутренний: 2
// Внешний: 3
// Внутренний: 1
// Внутренний: 2
Внутренний цикл останавливается при j = 3. Но внешний цикл продолжает работать! Каждая итерация i заново запускает внутренний цикл, который снова останавливается на j = 3.
Что если нужно выйти из обоих циклов сразу? Один из способов — использовать флаг:
let found = false;
const target = 6;
for (let i = 1; i <= 5 && !found; i++) {
for (let j = 1; j <= 5; j++) {
if (i * j === target) {
console.log(`Нашли: ${i} × ${j} = ${target}`);
found = true;
break; // выходим из внутреннего
}
}
}
// При found = true условие внешнего цикла "i <= 5 && !found" станет false
// Внешний цикл тоже остановится
⚠️ Помните: break всегда выходит только из ближайшего цикла. Для выхода из нескольких уровней нужен флаг или вынести код в функцию с return.
🎨 Практика: рисуем паттерн из звёздочек
Один из любимых примеров на вложенные циклы — рисование паттернов. Это весело и хорошо показывает, как работают два измерения. Нарисуем треугольник:
// Треугольник из звёздочек
const n = 5;
for (let i = 1; i <= n; i++) {
let row = "";
for (let j = 1; j <= i; j++) {
row += "* ";
}
console.log(row);
}
// Результат:
// *
// * *
// * * *
// * * * *
// * * * * *
Заметьте: внутренний цикл идёт j <= i, то есть на первой строке 1 звёздочка, на второй 2, на третьей 3... Именно поэтому получается треугольник.
// Прямоугольник
for (let i = 1; i <= 3; i++) {
let row = "";
for (let j = 1; j <= 5; j++) {
row += "# ";
}
console.log(row);
}
// Результат:
// # # # # #
// # # # # #
// # # # # #
И пример с матрицей — создадим и посчитаем сумму всех элементов матрицы N×N, где элемент [i][j] = i * j:
function matrixSum(n) {
let total = 0;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
total += i * j;
}
}
return total;
}
console.log(matrixSum(3)); // 0+0+0 + 0+1+2 + 0+2+4 = 9
console.log(matrixSum(4)); // 36
✅ Итоги урока
- Вложенные циклы — это цикл внутри цикла. Внутренний выполняется полностью при каждой итерации внешнего
- Количество итераций перемножается: внешний N × внутренний M = N×M операций всего
- Матрица в JS — массив массивов. Доступ:
matrix[строка][столбец] - Обход матрицы: внешний цикл по строкам (
i), внутренний по столбцам (j) - O(n²) — квадратичная сложность: данных в 10 раз больше → операций в 100 раз больше
breakво вложенном цикле выходит только из внутреннего, не из внешнего- Для выхода из нескольких циклов сразу — флаг или вынос в функцию с
return
Поздравляем! Вы освоили все основные виды циклов в JavaScript. Теперь вы умеете повторять действия, управлять потоком и работать с двумерными данными. 🚀
Тест по вложенным циклам
7 вопросов