NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Методы flat, flatMap, includes и Array.from()

JavaScript (ES6 и новее)Общие возможности ES6По умолчанию

Основная идея

Методы flat(), flatMap(), includes() и Array.from() — современные инструменты ES6+ для работы с массивами. Они упрощают «выравнивание» вложенных массивов, поиск элементов и создание массивов из iterable-объектов.

Ключевые аспекты

  • flat(depth) — выравнивает вложенные массивы на указанную глубину (по умолчанию 1)
  • flatMap(fn) — комбинация map() + flat(1), сначала преобразует, потом выравнивает на 1 уровень
  • includes(element) — проверяет наличие элемента в массиве, возвращает true/false
  • Array.from(iterable) — создаёт массив из iterable или array-like объекта

Плюсы

  • Декларативный и читаемый код
  • Встроены в язык — не нужны библиотеки
  • includes() корректно работает с NaN (в отличие от indexOf)
  • Array.from() позволяет задавать функцию-маппер вторым аргументом

Минусы

  • flat() и flatMap() не поддерживаются в IE
  • flat(Infinity) на очень глубоких структурах может быть медленным
  • flatMap() выравнивает только на 1 уровень — для большей глубины нужен отдельный flat()

Частые ошибки на собеседованиях

  • Путают includes() с indexOf()includes возвращает boolean, indexOf возвращает индекс
  • Не знают, что includes() находит NaN, а indexOf() — нет
  • Забывают про второй аргумент Array.from(iterable, mapFn) для преобразования
  • Думают, что flatMap() может выравнивать на произвольную глубину

Введение и проблематика

При работе с массивами часто возникают типичные задачи:

  • Выравнивание вложенных массивов
  • Проверка наличия элемента
  • Преобразование iterable-объектов в массивы

До ES6/ES2019 эти задачи решались через reduce(), indexOf(), Array.prototype.slice.call() и другие обходные пути. Современные методы делают код чище и понятнее.


Array.prototype.flat()

Метод flat() создаёт новый массив, «выравнивая» вложенные массивы на указанную глубину.

Синтаксис

js
array.flat(depth)
  • depth — глубина выравнивания (по умолчанию 1)
  • Возвращает новый массив (не мутирует оригинал)

Примеры

Code Example 1: Что вернёт каждый вызов flat()? Как задать глубину выравнивания?

js
// Глубина 1 (по умолчанию)
const arr1 = [1, [2, 3], [4, 5]];
console.log(arr1.flat()); // [1, 2, 3, 4, 5]
 
// Глубина 2
const arr2 = [1, [2, [3, [4]]]];
console.log(arr2.flat(2)); // [1, 2, 3, [4]]
 
// Бесконечная глубина
console.log(arr2.flat(Infinity)); // [1, 2, 3, 4]

Удаление пустых слотов

flat() автоматически удаляет «дырки» (empty slots) в массиве:

Code Example 2: Что произойдёт с пустыми слотами при вызове flat()?

js
const sparse = [1, , 3, , 5];
console.log(sparse.flat()); // [1, 3, 5]

Это полезно для очистки sparse-массивов, созданных через new Array(n) или с удалёнными элементами.


Array.prototype.flatMap()

Метод flatMap() — это комбинация map() и flat(1) в одном вызове. Сначала применяет функцию к каждому элементу, затем выравнивает результат на 1 уровень.

Синтаксис

js
array.flatMap(callback(element, index, array))

Пример: разбиение строк на слова

Code Example 3: В чём разница между map() и flatMap() в этом примере? Что будет в каждой переменной?

js
const sentences = ['Hello world', 'How are you'];
 
// С обычным map получим вложенные массивы
const withMap = sentences.map(s => s.split(' '));
console.log(withMap); // [['Hello', 'world'], ['How', 'are', 'you']]
 
// flatMap выравнивает результат
const withFlatMap = sentences.flatMap(s => s.split(' '));
console.log(withFlatMap); // ['Hello', 'world', 'How', 'are', 'you']

Пример: фильтрация и преобразование одновременно

Code Example 4: Как flatMap() используется для одновременной фильтрации и преобразования? Что будет в result?

js
const numbers = [1, 2, 3, 4, 5];
 
// Удваиваем только чётные числа
const result = numbers.flatMap(n =>
  n % 2 === 0 ? [n, n] : []
);
console.log(result); // [2, 2, 4, 4]
⚠️

flatMap() выравнивает только на один уровень. Для большей глубины используйте .map().flat(depth).

Сравнение производительности

Code Example 5: В чём разница между этими двумя подходами с точки зрения производительности?

js
// ✅ Один проход по массиву
const result = arr.flatMap(x => [x, x * 2]);

Array.prototype.includes()

Метод includes() проверяет, содержит ли массив указанный элемент, и возвращает true или false.

Синтаксис

js
array.includes(searchElement, fromIndex)
  • searchElement — искомый элемент
  • fromIndex — индекс, с которого начинать поиск (опционально)

Основное использование

js
const fruits = ['apple', 'banana', 'orange'];
 
console.log(fruits.includes('banana')); // true
console.log(fruits.includes('grape'));  // false
 
// Поиск с определённого индекса
console.log(fruits.includes('apple', 1)); // false (начинаем с индекса 1)

Преимущество перед indexOf()

Главное отличие — корректная работа с NaN:

Code Example 6: Почему indexOf не находит NaN, а includes находит?

js
const arr = [1, 2, NaN, 4];
 
// indexOf не находит NaN
console.log(arr.indexOf(NaN) !== -1); // false ❌
 
// includes находит NaN
console.log(arr.includes(NaN)); // true ✅

Это связано с тем, что indexOf() использует строгое сравнение (===), а NaN === NaN возвращает false. Метод includes() использует алгоритм SameValueZero, который корректно обрабатывает NaN.

Сравнение includes vs indexOf

Аспектincludes()indexOf()
Возвращаетbooleannumber (индекс или -1)
Находит NaN✅ Да❌ Нет
ЧитаемостьВышеТребует !== -1
ES версияES2016ES5

Array.from()

Статический метод Array.from() создаёт новый массив из iterable или array-like объекта.

Синтаксис

js
Array.from(arrayLike, mapFn, thisArg)
  • arrayLike — iterable или array-like объект
  • mapFn — функция преобразования (опционально)
  • thisArg — значение this для mapFn (опционально)

Преобразование array-like объектов

js
// NodeList из DOM
const divs = document.querySelectorAll('div');
const divArray = Array.from(divs);
 
// arguments в функции
function example() {
  const args = Array.from(arguments);
  console.log(args);
}

Преобразование строки

js
const str = 'Hello';
const chars = Array.from(str);
console.log(chars); // ['H', 'e', 'l', 'l', 'o']

Создание массива с заполнением

Code Example 7: Что будет в range и zeros? Как работает второй аргумент Array.from()?

js
// Массив чисел от 0 до 4
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range); // [0, 1, 2, 3, 4]
 
// Массив из 10 нулей
const zeros = Array.from({ length: 10 }, () => 0);
console.log(zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Второй аргумент mapFn работает эффективнее, чем Array.from(...).map(...), так как не создаёт промежуточный массив.

Работа с Set и Map

Code Example 8: Что вернёт Array.from() для Set и Map? Чем Array.from(set) отличается от [...set]?

js
// Set → Array
const set = new Set([1, 2, 2, 3]);
const arrFromSet = Array.from(set);
console.log(arrFromSet); // [1, 2, 3]
 
// Map → Array of arrays
const map = new Map([['a', 1], ['b', 2]]);
const arrFromMap = Array.from(map);
console.log(arrFromMap); // [['a', 1], ['b', 2]]

Альтернатива: spread оператор

js
// Эквивалентные записи для iterable
const arr1 = Array.from(set);
const arr2 = [...set];
 
// Но Array.from поддерживает mapFn
const doubled = Array.from(set, x => x * 2); // spread так не может

Практические примеры

Уникальные слова из текста

js
const text = 'hello world hello javascript world';
const words = text.split(' ');
const uniqueWords = Array.from(new Set(words));
console.log(uniqueWords); // ['hello', 'world', 'javascript']

Выравнивание вложенных данных с API

Code Example 9: Что будет в allTags? Как работает комбинация flatMap + Set + spread?

js
const users = [
  { name: 'John', tags: ['admin', 'editor'] },
  { name: 'Jane', tags: ['viewer'] }
];
 
// Получаем все уникальные теги
const allTags = [...new Set(users.flatMap(u => u.tags))];
console.log(allTags); // ['admin', 'editor', 'viewer']

Генерация последовательностей

js
// Чётные числа от 0 до 18
const evens = Array.from({ length: 10 }, (_, i) => i * 2);
console.log(evens); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Пограничные случаи

flat() с примитивами

js
// Примитивы остаются как есть
const mixed = [1, [2, 'three'], [[4]]];
console.log(mixed.flat(2)); // [1, 2, 'three', 4]

includes() с объектами

Code Example 10: Почему первый includes возвращает true, а второй — false?

js
const obj = { id: 1 };
const arr = [obj, { id: 2 }];
 
console.log(arr.includes(obj));     // true (тот же объект)
console.log(arr.includes({ id: 1 })); // false (другой объект)
⚠️

includes() сравнивает объекты по ссылке, а не по содержимому. Для поиска по свойствам используйте find() или some().


Поддержка браузерами

МетодChromeFirefoxSafariEdgeIE
includes()47+43+9+14+
Array.from()45+32+9+12+
flat()69+62+12+79+
flatMap()69+62+12+79+

Вопросы интервьюера

Q: Чем includes() лучше indexOf()?

includes() возвращает boolean (читаемее), корректно находит NaN, и семантически яснее показывает намерение — проверку наличия.

Q: Можно ли выравнивать на произвольную глубину с flatMap()?

Нет, flatMap() выравнивает только на 1 уровень. Для большей глубины используйте map().flat(depth) или рекурсивный подход.

Q: Как создать массив определённой длины с заполнением?

Array.from({ length: n }, () => value) или Array(n).fill(value) — но fill даёт одну ссылку для объектов!

Q: Мутирует ли flat() исходный массив?

Нет, flat() возвращает новый массив. Оригинал не изменяется.


Источники

Code Example 1: Array.prototype.flat()

❓ Что вернёт каждый вызов flat()? Как задать глубину выравнивания?

js
const arr1 = [1, [2, 3], [4, 5]];
console.log(arr1.flat());
 
const arr2 = [1, [2, [3, [4]]]];
console.log(arr2.flat(2));
 
console.log(arr2.flat(Infinity));

Code Example 2: flat() и пустые слоты

❓ Что произойдёт с пустыми слотами при вызове flat()?

js
const sparse = [1, , 3, , 5];
console.log(sparse.flat());

Code Example 3: flatMap() — разбиение строк

❓ В чём разница между map() и flatMap() в этом примере? Что будет в каждой переменной?

js
const sentences = ['Hello world', 'How are you'];
 
const withMap = sentences.map(s => s.split(' '));
console.log(withMap);
 
const withFlatMap = sentences.flatMap(s => s.split(' '));
console.log(withFlatMap);

Code Example 4: flatMap() — фильтрация и преобразование

❓ Как flatMap() используется для одновременной фильтрации и преобразования? Что будет в result?

js
const numbers = [1, 2, 3, 4, 5];
 
const result = numbers.flatMap(n =>
  n % 2 === 0 ? [n, n] : []
);
console.log(result);

Code Example 5: flatMap vs map + flat

❓ В чём разница между этими двумя подходами с точки зрения производительности?

Version A:

js
const result = arr.flatMap(x => [x, x * 2]);

Version B:

js
const result = arr.map(x => [x, x * 2]).flat();

Code Example 6: includes() и NaN

❓ Почему indexOf не находит NaN, а includes находит?

js
const arr = [1, 2, NaN, 4];
 
console.log(arr.indexOf(NaN) !== -1);
console.log(arr.includes(NaN));

Code Example 7: Array.from() — создание массива с заполнением

❓ Что будет в range и zeros? Как работает второй аргумент Array.from()?

js
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range);
 
const zeros = Array.from({ length: 10 }, () => 0);
console.log(zeros);

Code Example 8: Array.from() с Set и Map

❓ Что вернёт Array.from() для Set и Map? Чем Array.from(set) отличается от [...set]?

js
const set = new Set([1, 2, 2, 3]);
const arrFromSet = Array.from(set);
console.log(arrFromSet);
 
const map = new Map([['a', 1], ['b', 2]]);
const arrFromMap = Array.from(map);
console.log(arrFromMap);
 
const doubled = Array.from(set, x => x * 2);

Code Example 9: Практический пример — уникальные теги

❓ Что будет в allTags? Как работает комбинация flatMap + Set + spread?

js
const users = [
  { name: 'John', tags: ['admin', 'editor'] },
  { name: 'Jane', tags: ['viewer'] }
];
 
const allTags = [...new Set(users.flatMap(u => u.tags))];
console.log(allTags);

Code Example 10: includes() с объектами

❓ Почему первый includes возвращает true, а второй — false?

js
const obj = { id: 1 };
const arr = [obj, { id: 2 }];
 
console.log(arr.includes(obj));
console.log(arr.includes({ id: 1 }));