Создаем двумерные массивы в JavaScript: 5 эффективных методов

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • 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%. Это изменение превратило тормозящий интерфейс в плавно работающий продукт, которым клиенты теперь довольны.

Пошаговый план для смены профессии

Создание матриц с помощью вложенных массивов

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

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

JS
Скопировать код
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];

Этот способ имеет ряд преимуществ:

  • Высокая читаемость кода — структура массива визуально понятна
  • Прямой контроль над значениями — каждый элемент явно определён
  • Возможность создавать нерегулярные массивы (с разным количеством элементов в строках)

Для создания пустого двумерного массива определённой размерности можно использовать похожий подход:

JS
Скопировать код
const emptyMatrix = [
new Array(3),
new Array(3),
new Array(3)
];

Однако такой метод имеет подводные камни. Главная проблема — созданные с помощью конструктора Array(n) массивы будут разреженными, то есть содержать пустые слоты, а не фактические значения undefined. Это может привести к неожиданным результатам при использовании методов массива.

Для более сложных случаев удобно использовать вложенные литералы массивов с заполнением:

JS
Скопировать код
// Создание матрицы 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, который часто недооценивают при работе с двумерными массивами. Он позволяет создавать новые массивы на основе итерируемых объектов и предлагает элегантный синтаксис для инициализации матриц любой размерности. 🔄

Базовый синтаксис для создания двумерного массива выглядит так:

JS
Скопировать код
const matrix = Array.from({ length: rows }, () => 
Array.from({ length: columns }, () => initialValue)
);

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

JS
Скопировать код
// Создание матрицы 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 Проекты с функциональной парадигмой
Инициализация с индексами Доступ к индексам строк и столбцов при создании Матрицы с зависимыми от позиции значениями
Производительность Работает быстрее вложенных циклов для больших массивов Большие матрицы данных

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

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 выглядит так:

JS
Скопировать код
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);

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

JS
Скопировать код
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() для большей безопасности:

JS
Скопировать код
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+

Циклы особенно полезны при создании матриц со сложными паттернами:

JS
Скопировать код
// Создание спиральной матрицы
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-разработчика, несмотря на появление более современных и лаконичных методов.

Эффективный доступ и манипуляции с элементами массивов

Создание двумерного массива — лишь первый шаг. Для эффективной работы с такими структурами необходимо понимать оптимальные способы доступа к элементам и их модификации. Правильные техники манипуляции могут значительно повысить производительность вашего приложения, особенно при работе с большими объемами данных. 🛠️

Базовый доступ к элементу двумерного массива осуществляется через двойную индексацию:

JS
Скопировать код
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 — продвинутый функциональный подход

Сравнение различных методов обхода:

JS
Скопировать код
// Вложенные 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);
});

Для манипуляции элементами матрицы можно использовать различные подходы:

JS
Скопировать код
// Создание копии матрицы (глубокое копирование)
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) подходы

Пример оптимизированного кода для вычисления суммы всех элементов матрицы:

JS
Скопировать код
// Менее оптимизированный вариант
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-разработчика.

Загрузка...