Цепочки методов массивов в JavaScript

Цепочка методов массивов - это запись, где результат одного метода сразу передается следующему методу.

let numbers = [1, 2, 3, 4, 5, 6];

let result = numbers
  .filter((number) => number % 2 === 0)
  .map((number) => number * 10);

console.log(result); // [20, 40, 60]

Сначала filter() оставляет только четные числа. Потом map() умножает каждое из них на десять.

Что такое цепочка методов

Некоторые методы массивов возвращают новый массив. Например, filter() и map(). Поэтому после них можно сразу вызвать еще один метод массива.

let numbers = [1, 2, 3, 4];

let result = numbers.filter((number) => number > 2).map((number) => number * 2);

Такая запись корректна, но длинную цепочку обычно удобнее переносить на несколько строк.

let result = numbers
  .filter((number) => number > 2)
  .map((number) => number * 2);

Так легче увидеть каждый шаг.

Простой пример

Допустим, есть список товаров. Нужно взять только товары в наличии и получить их названия.

let products = [
  { title: 'Книга', price: 500, inStock: true },
  { title: 'Курс', price: 3000, inStock: false },
  { title: 'Подписка', price: 900, inStock: true },
];

let titles = products
  .filter((product) => product.inStock)
  .map((product) => product.title);

console.log(titles); // ['Книга', 'Подписка']

Здесь цепочка читается почти как инструкция: сначала отфильтровать, потом преобразовать.

Когда использовать цепочку

Цепочка хорошо подходит, когда несколько действий идут одно за другим и каждое действие достаточно простое.

let users = [
  { name: 'Анна', age: 22, active: true },
  { name: 'Олег', age: 17, active: true },
  { name: 'Мария', age: 30, active: false },
];

let names = users
  .filter((user) => user.active)
  .filter((user) => user.age >= 18)
  .map((user) => user.name);

console.log(names); // ['Анна']

Такая цепочка уместна, потому что каждый шаг решает одну понятную задачу.

Обычно цепочка читается нормально, если в ней два-три простых шага. Если шагов становится больше, код может начать выглядеть тяжелым.

Когда лучше разбить на переменные

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

let activeUsers = users.filter((user) => user.active);

let adultUsers = activeUsers.filter((user) => user.age >= 18);

let names = adultUsers.map((user) => user.name);

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

Разбивать цепочку полезно, когда нужно посмотреть результат после отдельного шага через console.log().

console.log(activeUsers);
console.log(adultUsers);

Порядок методов

В цепочке важен порядок. Каждый следующий метод работает уже не с исходным массивом, а с результатом предыдущего шага.

let users = [
  { name: 'Анна', active: true },
  { name: 'Олег', active: false },
];

let names = users
  .filter((user) => user.active)
  .map((user) => user.name);

console.log(names); // ['Анна']

Если сначала сделать map(), то дальше в массиве будут уже строки, а не объекты.

let names = users
  .map((user) => user.name)
  .filter((name) => name.length > 3);

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

Какие методы можно ставить в цепочку

В середине цепочки обычно используют методы, которые возвращают массив: map(), filter(), slice(), concat().

Методы, которые возвращают не массив, чаще завершают цепочку.

let total = products
  .filter((product) => product.inStock)
  .reduce((sum, product) => sum + product.price, 0);

console.log(total); // 1400

Здесь filter() возвращает массив, поэтому после него можно вызвать reduce(). А reduce() возвращает число, поэтому цепочка на нем заканчивается.

forEach() в цепочках обычно не используют, потому что он возвращает undefined.

let result = products.forEach((product) => {
  console.log(product.title);
});

console.log(result); // undefined

Итого

1. Цепочка методов позволяет передавать результат одного метода сразу в следующий.

2. Цепочки хорошо подходят для простых последовательных действий, например filter().map().

3. Если цепочка становится длинной, ее лучше разбить на переменные.

4. Порядок методов важен, потому что каждый следующий метод работает с результатом предыдущего.

5. Методы вроде reduce(), find() и some() чаще завершают цепочку, а forEach() для цепочек обычно не подходит.