Создаем двумерные массивы в JavaScript: 5 эффективных методов
Для кого эта статья:
- JavaScript-разработчики, имеющие опыт работы с двумерными массивами
- Студенты и начинающие программисты, изучающие JavaScript
Профессионалы, заинтересованные в улучшении производительности кода и оптимизации работы с массивами
Любой уважающий себя JavaScript-разработчик рано или поздно сталкивается с необходимостью создания двумерных массивов. Будь то игровое поле, таблица данных или матрица для сложных вычислений — эта структура данных остаётся незаменимой для множества задач. Однако в JavaScript, в отличие от некоторых других языков, двумерные массивы реализуются не так очевидно. Многие разработчики используют лишь один способ создания, не подозревая о существовании более элегантных и производительных решений. В этой статье я раскрою пять проверенных методов, которые существенно упростят вашу работу с матрицами данных. 🚀
Хотите глубже понять не только двумерные массивы, но и все тонкости работы с данными в JavaScript? Программа Обучение веб-разработке от Skypro построена на реальных задачах и проектах. Вы не просто узнаете теорию, но сразу применяете знания на практике — от базовых структур данных до сложных фронтенд-архитектур. Менторы с опытом в IT-гигантах проверят ваш код и подскажут, как сделать его более чистым и производительным.
Двумерные массивы в JavaScript: основные концепции
В JavaScript нет "настоящих" двумерных массивов, как в некоторых низкоуровневых языках программирования. То, что мы называем двумерным массивом, технически представляет собой массив массивов — структуру, где каждый элемент основного массива сам является массивом. Это ключевой момент для понимания, который влияет на все операции с такими структурами. ⚙️
Концептуально двумерный массив можно представить в виде таблицы с рядами и колонками:
| [0,0] | [0,1] | [0,2] |
| [1,0] | [1,1] | [1,2] |
| [2,0] | [2,1] | [2,2] |
Где первый индекс указывает на ряд (строку), а второй — на колонку (столбец).
Прежде чем перейти к методам создания, важно понимать некоторые базовые свойства двумерных массивов в JavaScript:
- Нерегулярность — внутренние массивы могут иметь разную длину
- Разреженность — некоторые ячейки могут отсутствовать или содержать undefined
- Память — каждый вложенный массив — это отдельный объект, что влияет на производительность
- Передача по ссылке — при копировании двумерного массива требуются особые подходы
Максим Рогов, тимлид JavaScript-разработки
В начале карьеры я использовал только циклы для создания матриц в JavaScript. Это выглядело примерно так: сначала создаём пустой массив, затем в двойном цикле заполняем его значениями. Подход работал, но код получался громоздким и плохо читаемым.
На одном из проектов нам нужно было визуализировать огромный набор данных в виде интерактивной тепловой карты. Каждая ячейка представляла точку данных, и производительность стала критическим фактором. Тогда я открыл для себя метод Array.from() — он позволил не только сократить объём кода в три раза, но и ускорил инициализацию массива на 40%. Это изменение превратило тормозящий интерфейс в плавно работающий продукт, которым клиенты теперь довольны.

Создание матриц с помощью вложенных массивов
Самый интуитивно понятный метод создания двумерного массива — это явное вложение массивов друг в друга. Этот подход особенно удобен, когда вы заранее знаете все значения или размерность матрицы невелика.
Базовый синтаксис выглядит следующим образом:
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
Этот способ имеет ряд преимуществ:
- Высокая читаемость кода — структура массива визуально понятна
- Прямой контроль над значениями — каждый элемент явно определён
- Возможность создавать нерегулярные массивы (с разным количеством элементов в строках)
Для создания пустого двумерного массива определённой размерности можно использовать похожий подход:
const emptyMatrix = [
new Array(3),
new Array(3),
new Array(3)
];
Однако такой метод имеет подводные камни. Главная проблема — созданные с помощью конструктора Array(n) массивы будут разреженными, то есть содержать пустые слоты, а не фактические значения undefined. Это может привести к неожиданным результатам при использовании методов массива.
Для более сложных случаев удобно использовать вложенные литералы массивов с заполнением:
// Создание матрицы 3x3 с заполнением по умолчанию
const filledMatrix = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
];
// Шахматная доска 8x8
const chessboard = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
];
Основной недостаток метода вложенных массивов — его неэффективность при создании больших матриц с повторяющимися значениями. В таких случаях лучше использовать программные подходы, которые мы рассмотрим далее.
Метод Array.from() для инициализации двумерного массива
Array.from() — это мощный метод, появившийся в ES6, который часто недооценивают при работе с двумерными массивами. Он позволяет создавать новые массивы на основе итерируемых объектов и предлагает элегантный синтаксис для инициализации матриц любой размерности. 🔄
Базовый синтаксис для создания двумерного массива выглядит так:
const matrix = Array.from({ length: rows }, () =>
Array.from({ length: columns }, () => initialValue)
);
Этот метод особенно удобен, когда нужно создать массив с предустановленными значениями:
// Создание матрицы 3x3 заполненной нулями
const zeroMatrix = Array.from({ length: 3 }, () =>
Array.from({ length: 3 }, () => 0)
);
// Создание матрицы 4x4 с возрастающими числами
const sequenceMatrix = Array.from({ length: 4 }, (_, rowIndex) =>
Array.from({ length: 4 }, (_, colIndex) => rowIndex * 4 + colIndex + 1)
);
Преимущества метода Array.from() для создания двумерных массивов:
| Преимущество | Описание | Применимость |
|---|---|---|
| Краткость | Создание заполненной матрицы в 1-2 строки кода | Любые двумерные массивы |
| Функциональный стиль | Соответствует современным функциональным паттернам JS | Проекты с функциональной парадигмой |
| Инициализация с индексами | Доступ к индексам строк и столбцов при создании | Матрицы с зависимыми от позиции значениями |
| Производительность | Работает быстрее вложенных циклов для больших массивов | Большие матрицы данных |
Этот метод также позволяет создавать сложные структуры с вычисляемыми значениями:
// Таблица умножения 10x10
const multiplicationTable = Array.from({ length: 10 }, (_, i) =>
Array.from({ length: 10 }, (_, j) => (i + 1) * (j + 1))
);
// Матрица расстояний между точками
const points = [[1, 2], [3, 4], [5, 6]];
const distanceMatrix = Array.from({ length: points.length }, (_, i) =>
Array.from({ length: points.length }, (_, j) => {
const [x1, y1] = points[i];
const [x2, y2] = points[j];
return Math.sqrt(Math.pow(x2 – x1, 2) + Math.pow(y2 – y1, 2));
})
);
Один важный нюанс при работе с Array.from(): каждый вызов функции маппинга создаёт новый массив, что гарантирует отсутствие проблем с ссылками, которые могут возникнуть при использовании других методов.
Анна Свирская, JS-архитектор
Я столкнулась с неочевидной проблемой при работе над визуализатором игры "Жизнь Конвея". Изначально создавала двумерный массив с использованием метода fill() и map():
JSСкопировать кодconst grid = new Array(height).fill().map(() => new Array(width).fill(false));Всё работало прекрасно, пока тестировщик не обнаружил странную ошибку: иногда целые столбцы клеток оживали одновременно. После долгих исследований выяснила, что проблема крылась в методе fill() — он заполнял массив ссылками на один и тот же внутренний массив!
Когда я переписала код с использованием Array.from():
JSСкопировать кодconst grid = Array.from({ length: height }, () => Array.from({ length: width }, () => false) );Проблема исчезла. Этот случай стал для меня важным уроком о внутреннем устройстве JavaScript и теперь я всегда использую Array.from() для надёжного создания двумерных массивов.
Использование циклов для формирования матриц
Несмотря на наличие более современных методов, создание двумерных массивов с помощью циклов остаётся универсальным подходом, который дает полный контроль над процессом и работает во всех версиях JavaScript. Этот метод особенно полезен при сложной логике инициализации или работе с унаследованным кодом. 🔄
Классический способ с использованием вложенных циклов for выглядит так:
function createMatrix(rows, cols, initialValue) {
const matrix = [];
for (let i = 0; i < rows; i++) {
matrix[i] = [];
for (let j = 0; j < cols; j++) {
matrix[i][j] = initialValue;
}
}
return matrix;
}
// Создание матрицы 4x4 заполненной нулями
const zeroMatrix = createMatrix(4, 4, 0);
Циклы дают возможность применять более сложную логику при инициализации:
function createCustomMatrix(rows, cols, initFunction) {
const matrix = [];
for (let i = 0; i < rows; i++) {
matrix[i] = [];
for (let j = 0; j < cols; j++) {
matrix[i][j] = initFunction(i, j);
}
}
return matrix;
}
// Матрица с биномиальными коэффициентами
const binomialMatrix = createCustomMatrix(5, 5, (i, j) => {
if (j > i) return 0;
if (j === 0 || j === i) return 1;
return binomialMatrix[i-1][j-1] + binomialMatrix[i-1][j];
});
Существуют различные вариации цикличного подхода, которые могут быть более подходящими в конкретных ситуациях:
- Использование push() вместо прямого присваивания — более безопасно при работе с индексами
- Циклы while/do-while — когда условие инициализации сложнее, чем просто перебор индексов
- Цикл for-of с Array(n).keys() — более современный подход с сохранением читаемости
Пример использования push() для большей безопасности:
function createSafeMatrix(rows, cols, initialValue) {
const matrix = [];
for (let i = 0; i < rows; i++) {
const row = [];
for (let j = 0; j < cols; j++) {
row.push(initialValue);
}
matrix.push(row);
}
return matrix;
}
Цикличный подход имеет свои преимущества и недостатки:
| Аспект | Преимущества | Недостатки |
|---|---|---|
| Читаемость | Понятна даже новичкам | Многострочность при простых задачах |
| Гибкость | Полный контроль над процессом | Больше кода для простых случаев |
| Производительность | Возможность оптимизации для конкретных случаев | Может быть медленнее встроенных методов |
| Совместимость | Работает во всех версиях JavaScript | Устаревший подход в сравнении с ES6+ |
Циклы особенно полезны при создании матриц со сложными паттернами:
// Создание спиральной матрицы
function createSpiralMatrix(n) {
const result = Array(n).fill().map(() => Array(n).fill(0));
let counter = 1;
let startRow = 0, endRow = n – 1;
let startCol = 0, endCol = n – 1;
while (startRow <= endRow && startCol <= endCol) {
// Верхняя строка
for (let i = startCol; i <= endCol; i++) {
result[startRow][i] = counter++;
}
startRow++;
// Правый столбец
for (let i = startRow; i <= endRow; i++) {
result[i][endCol] = counter++;
}
endCol--;
// Нижняя строка
if (startRow <= endRow) {
for (let i = endCol; i >= startCol; i--) {
result[endRow][i] = counter++;
}
endRow--;
}
// Левый столбец
if (startCol <= endCol) {
for (let i = endRow; i >= startRow; i--) {
result[i][startCol] = counter++;
}
startCol++;
}
}
return result;
}
Циклы остаются надёжным фундаментальным инструментом, который должен быть в арсенале каждого JavaScript-разработчика, несмотря на появление более современных и лаконичных методов.
Эффективный доступ и манипуляции с элементами массивов
Создание двумерного массива — лишь первый шаг. Для эффективной работы с такими структурами необходимо понимать оптимальные способы доступа к элементам и их модификации. Правильные техники манипуляции могут значительно повысить производительность вашего приложения, особенно при работе с большими объемами данных. 🛠️
Базовый доступ к элементу двумерного массива осуществляется через двойную индексацию:
const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// Получение значения (строка 1, столбец 2)
const value = matrix[1][2]; // 6
// Установка значения
matrix[0][1] = 10; // Теперь первая строка: [1, 10, 3]
При работе с двумерными массивами часто требуется их полный обход. Существует несколько способов сделать это:
- Вложенные циклы for — классический подход, подходящий для большинства случаев
- forEach с вложенным forEach — функциональный подход с лучшей читаемостью
- for...of с вложенным for...of — современный подход с хорошей читаемостью
- Деструктуризация с map/reduce — продвинутый функциональный подход
Сравнение различных методов обхода:
// Вложенные for
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
console.log(matrix[i][j]);
}
}
// forEach
matrix.forEach(row => {
row.forEach(element => {
console.log(element);
});
});
// for...of
for (const row of matrix) {
for (const element of row) {
console.log(element);
}
}
// Функциональный подход с flat()
matrix.flat().forEach(element => {
console.log(element);
});
Для манипуляции элементами матрицы можно использовать различные подходы:
// Создание копии матрицы (глубокое копирование)
const copyMatrix = matrix.map(row => [...row]);
// Транспонирование матрицы
const transposeMatrix = matrix[0].map((_, colIndex) =>
matrix.map(row => row[colIndex])
);
// Поиск элемента в матрице
function findInMatrix(matrix, value) {
for (let i = 0; i < matrix.length; i++) {
const j = matrix[i].indexOf(value);
if (j !== -1) {
return [i, j]; // [строка, столбец]
}
}
return null; // Элемент не найден
}
// Сложение двух матриц
function addMatrices(matrix1, matrix2) {
return matrix1.map((row, i) =>
row.map((value, j) => value + matrix2[i][j])
);
}
Советы для оптимизации производительности при работе с двумерными массивами:
- Минимизируйте количество операций доступа к элементам массива в циклах
- Используйте кэширование длины массива (const len = arr.length) при циклическом обходе
- При вставке/удалении предпочитайте работу с рядами, а не с отдельными элементами
- Для разреженных матриц рассмотрите использование объекта вместо классических двумерных массивов
- Для операций, не меняющих исходную матрицу, используйте неизменяемые (immutable) подходы
Пример оптимизированного кода для вычисления суммы всех элементов матрицы:
// Менее оптимизированный вариант
function sumMatrix(matrix) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
sum += matrix[i][j];
}
}
return sum;
}
// Оптимизированный вариант
function optimizedSumMatrix(matrix) {
let sum = 0;
const rows = matrix.length;
for (let i = 0; i < rows; i++) {
const row = matrix[i];
const cols = row.length;
for (let j = 0; j < cols; j++) {
sum += row[j];
}
}
return sum;
}
// Функциональный оптимизированный вариант
const functionalSumMatrix = matrix =>
matrix.reduce((sum, row) => sum + row.reduce((rowSum, val) => rowSum + val, 0), 0);
Правильный выбор методов доступа и манипуляции с элементами двумерных массивов может значительно упростить код и повысить его производительность, что особенно важно в приложениях с интенсивной обработкой данных.
Работа с двумерными массивами в JavaScript открывает широкие возможности для структурирования и обработки данных. Выбор подходящего метода создания и манипуляции зависит от конкретной задачи, требований к читаемости кода и производительности. Для небольших матриц с заранее известными значениями подойдет метод вложенных массивов. При необходимости программного создания массивов предпочтительнее использовать Array.from() благодаря его элегантности и отсутствию проблем с ссылками. Циклы остаются универсальным инструментом, когда требуется сложная логика инициализации. Помните, что эффективность кода зависит не только от способа создания массива, но и от техник доступа к его элементам в дальнейшем. Освоив все эти методы, вы значительно расширите свой инструментарий JavaScript-разработчика.