Avatar ·

Как объединить (смержить/сплющить) массив массивов в JavaScript?

Всем привет! Я новичок в JavaScript и столкнулся с проблемой при работе с вложенными массивами. У меня есть массив, который содержит внутри себя несколько других массивов: ```javascript const nestedArrays = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; ``` Мне нужно превратить его в один плоский массив, чтобы получить: ```javascript [1, 2, 3, 4, 5, 6, 7, 8, 9] ``` Я пробовал использовать метод `concat()`, но он не работает так, как я ожидал: ```javascript const result = nestedArrays.concat(); // Результат: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] — ничего не изменилось ``` Пробовал написать цикл, но запутался. Также у меня есть более сложный случай: массив может содержать не только числа, но и другие массивы внутри (глубокая вложенность), например: ```javascript const deepNested = [1, [2, [3, [4, 5]]], 6]; ``` Как правильно "сплющить" такой массив в одну строку? Есть ли встроенные методы в современном JavaScript для этого? Или нужно писать свою функцию? Буду благодарен за понятное объяснение с примерами кода. <<>>

Решение: объединение массивов в JavaScript

Проблема, которую вы описали, называется flattening (сплющивание/выравнивание) массива. В современном JavaScript есть несколько способов это сделать. Давайте разберем их от простого к сложному.

1. Метод flat() (ES2019) — самый простой способ

Начиная с ECMAScript 2019, у массивов появился встроенный метод flat(). Он принимает один необязательный аргумент — глубину вложенности. По умолчанию глубина = 1.

// Простой случай (один уровень вложенности)
const nestedArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const flattened = nestedArrays.flat();
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Глубокая вложенность — укажите глубину
const deepNested = [1, [2, [3, [4, 5]]], 6];
const deepFlattened = deepNested.flat(2); // сплющит на 2 уровня
console.log(deepFlattened); // [1, 2, 3, [4, 5], 6] — массив [4,5] остался

// Чтобы сплющить полностью, используйте Infinity
const fullyFlattened = deepNested.flat(Infinity);
console.log(fullyFlattened); // [1, 2, 3, 4, 5, 6]

2. Классический подход через reduce() и concat()

Если вам нужно поддерживать старые браузеры или вы хотите понять, как это работает "под капотом":

const nestedArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

const flattened = nestedArrays.reduce((acc, current) => {
  return acc.concat(current);
}, []);

console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Для глубокой вложенности — рекурсивная версия
function flattenDeep(arr) {
  return arr.reduce((acc, val) => {
    if (Array.isArray(val)) {
      return acc.concat(flattenDeep(val));
    } else {
      return acc.concat(val);
    }
  }, []);
}

const deepNested = [1, [2, [3, [4, 5]]], 6];
console.log(flattenDeep(deepNested)); // [1, 2, 3, 4, 5, 6]

3. Использование spread-оператора (для одного уровня)

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

const nestedArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const flattened = [].concat(...nestedArrays);
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Или с помощью spread в новом массиве
const flattened2 = [...nestedArrays[0], ...nestedArrays[1], ...nestedArrays[2]];
// Но это неудобно, если массивов много

4. Метод flatMap() (если нужно трансформировать)

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

const arrays = [[1, 2], [3, 4], [5, 6]];
const result = arrays.flatMap(arr => arr.map(x => x * 2));
console.log(result); // [2, 4, 6, 8, 10, 12]

Важные моменты:

  • Производительность: flat() работает быстрее всего, так как это нативный метод
  • Совместимость: flat() поддерживается во всех современных браузерах и Node.js 11+. Для старых окружений используйте reduce()
  • Пустые элементы: flat() удаляет пустые слоты из массива ([1, , 3].flat() → [1, 3])
  • Глубина: Всегда используйте Infinity, если не знаете точную глубину вложенности

Рекомендую использовать flat(Infinity) для большинства задач — это самый элегантный и безопасный способ.

<<>>

Альтернативный подход: ручная рекурсия и генераторы

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

Рекурсивная функция с циклом

function flattenArray(arr) {
  const result = [];
  
  for (let i = 0; i < arr.length; i++) {
    const element = arr[i];
    if (Array.isArray(element)) {
      // Если элемент — массив, рекурсивно сплющиваем его
      const flatSubArray = flattenArray(element);
      // Добавляем все элементы из сплющенного подмассива
      for (let j = 0; j < flatSubArray.length; j++) {
        result.push(flatSubArray[j]);
      }
    } else {
      // Если не массив — просто добавляем
      result.push(element);
    }
  }
  
  return result;
}

const deepNested = [1, [2, [3, [4, 5]]], 6];
console.log(flattenArray(deepNested)); // [1, 2, 3, 4, 5, 6]

Использование генератора (для ленивых вычислений)

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

function* flattenGenerator(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flattenGenerator(item); // рекурсивно делегируем
    } else {
      yield item;
    }
  }
}

const deepNested = [1, [2, [3, [4, 5]]], 6];
const flatIterator = flattenGenerator(deepNested);
console.log([...flatIterator]); // [1, 2, 3, 4, 5, 6]

Плюсы этого подхода:

  • Полный контроль над логикой
  • Генераторы экономят память при работе с большими данными
  • Можно легко модифицировать (например, добавлять отсев пустых значений)

Минусы:

  • Кода больше, чем при использовании flat()
  • Медленнее нативных методов

Для продакшена используйте flat(), а эти подходы — для глубокого понимания или нестандартных задач.