← Назад к вопросам

Можно ли цепочку вызовов применять к массиву?

1.8 Middle🔥 171 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли цепочку вызовов применять к массиву?

Да, абсолютно можно! Метод Chaining (цепочка вызовов) — это мощный паттерн проектирования, который часто используется с массивами. Встроенные методы JavaScript массивов (map, filter, reduce) идеально подходят для создания цепочек вызовов, потому что они возвращают новые массивы или значения, которые позволяют продолжить работу.

Встроенные методы для chaining

Самый простой и удобный способ — использовать встроенные методы массива:

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

const result = numbers
  .filter(n => n % 2 === 0)     // [2, 4, 6, 8, 10]
  .map(n => n * 2)               // [4, 8, 12, 16, 20]
  .filter(n => n > 10)           // [12, 16, 20]
  .reduce((sum, n) => sum + n, 0); // 48

console.log(result); // 48

Этот подход читаем, декларативен и не требует дополнительных библиотек. Каждый метод возвращает новый массив (или значение для reduce), что позволяет продолжить цепочку.

Методы для Chaining

Не все методы возвращают что-то полезное для цепочки:

// Методы, подходящие для chaining:
const chainable = [
  'map()',        // Возвращает новый массив
  'filter()',     // Возвращает новый массив
  'flatMap()',    // Возвращает новый массив
  'flat()',       // Возвращает новый массив
  'reverse()',    // Возвращает изменённый массив
  'sort()',       // Возвращает изменённый массив
  'reduce()',     // Возвращает значение (конец цепочки)
  'reduceRight()', // Возвращает значение (конец цепочки)
  'concat()'      // Возвращает новый массив
];

// Методы, НЕ подходящие для chaining:
const notChainable = [
  'forEach()',    // Возвращает undefined
  'push()',       // Возвращает длину
  'pop()',        // Возвращает удалённый элемент
  'splice()',     // Возвращает удалённые элементы
  'find()',       // Возвращает элемент или undefined
  'indexOf()',    // Возвращает индекс или -1
  'includes()'    // Возвращает boolean
];

Кастомный класс для более сложного chaining

Если встроенные методы недостаточны, создай кастомный класс:

class ArrayQuery {
  constructor(data = []) {
    this.data = data;
  }
  
  // Каждый метод должен возвращать this
  filter(predicate) {
    this.data = this.data.filter(predicate);
    return this;
  }
  
  map(mapper) {
    this.data = this.data.map(mapper);
    return this;
  }
  
  take(count) {
    this.data = this.data.slice(0, count);
    return this;
  }
  
  skip(count) {
    this.data = this.data.slice(count);
    return this;
  }
  
  orderBy(selector) {
    this.data = this.data.sort((a, b) => {
      const aVal = selector(a);
      const bVal = selector(b);
      return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
    });
    return this;
  }
  
  distinct() {
    this.data = [...new Set(this.data)];
    return this;
  }
  
  // Финальный метод для получения результата
  toArray() {
    return this.data;
  }
  
  // Альтернатива для получения первого элемента
  first() {
    return this.data[0];
  }
  
  // Получить количество элементов
  count() {
    return this.data.length;
  }
}

// Использование:
const result = new ArrayQuery([
  { name: 'John', age: 30, city: 'NYC' },
  { name: 'Jane', age: 25, city: 'LA' },
  { name: 'Bob', age: 30, city: 'NYC' },
  { name: 'Alice', age: 28, city: 'NYC' }
])
  .filter(p => p.age >= 28)
  .orderBy(p => p.age)
  .map(p => ({ name: p.name, age: p.age }))
  .take(2)
  .toArray();

// result: [
//   { name: 'Alice', age: 28 },
//   { name: 'John', age: 30 }
// ]

Ленивые вычисления (Lazy Evaluation)

Для больших массивов можно создать ленивый chaining:

class LazyQuery {
  constructor(data = []) {
    this.data = data;
    this.operations = [];
  }
  
  filter(predicate) {
    this.operations.push(('filter', predicate));
    return this;
  }
  
  map(mapper) {
    this.operations.push(('map', mapper));
    return this;
  }
  
  // Выполнить все операции только при вызове toArray()
  toArray() {
    return this.data.reduce((result, item) => {
      let current = item;
      
      for (const [op, fn] of this.operations) {
        if (op === 'filter' && !fn(current)) return result;
        if (op === 'map') current = fn(current);
      }
      
      return [...result, current];
    }, []);
  }
}

Lodash-style Chaining

Популярная библиотека Lodash предлагает собственный chain механизм:

// Без Lodash (встроенный подход)
const result = [1, 2, 3, 4, 5]
  .filter(n => n > 2)
  .map(n => n * 2);

// С Lodash
const _ = require('lodash');
const result = _.chain([1, 2, 3, 4, 5])
  .filter(n => n > 2)
  .map(n => n * 2)
  .value();  // Получить результат

Производительность

const hugeArray = Array.from({ length: 1000000 }, (_, i) => i);

// Способ 1: Встроенный chaining (создаёт промежуточные массивы)
const result1 = hugeArray
  .filter(n => n % 2 === 0)  // Промежуточный массив из 500k элементов
  .map(n => n * 2);          // Ещё один промежуточный массив
// Память: O(n)

// Способ 2: Ленивый chaining (ленивые вычисления)
const result2 = new LazyQuery(hugeArray)
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .toArray();
// Память: O(1)

Лучшие практики

  1. Используй встроенные методы когда возможно — они оптимизированы
  2. Возвращай this в кастомных методах для поддержки цепочки
  3. Добавь финальный метод (toArray, value) для получения результата
  4. Документируй порядок методов для читаемости
  5. Избегай побочных эффектов в методах chaining
// Хорошо: функциональный подход
const result = array
  .filter(x => x > 0)
  .map(x => x * 2);

// Плохо: побочные эффекты
const result = array
  .map(x => {
    console.log(x);  // Побочный эффект!
    return x * 2;
  });

Method chaining делает код более читаемым, функциональным и выразительным. Это один из самых красивых паттернов в JavaScript.

Можно ли цепочку вызовов применять к массиву? | PrepBro