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

Проблема:

Некоторые уменьшающие петли неэффективны. Скрипты, которым необходимо анализировать большой объем данных, могут зависать. Как работать лучше?

Предыстория проблемы

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

Предлагаемое решение - напомнить каждой функции, как распределять память.

Решения: сохранить ссылку на значение аккумулятора

Типичная конструкция функции сокращения в JS:

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

Итак, давайте сделаем два варианта:
1. это выглядит красиво, но неэффективно.

arr.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {});

2. это работает быстро, но выглядит «непривлекательно»

arr.reduce((acc, curr) => {
  acc[curr.id] = curr;
  return acc;
}, {});

На первый взгляд все то же самое, но на первый взгляд мне кажется лучше. Но давайте посмотрим, как работают эти функции. Репо с тестами производительности уже делал. Для запуска тестовых наборов используйте Benchmark.js, который позволяет мне делать простые тесты, и фейкер для имитации данных.

Эксперимент:

const Benchmark = require('benchmark');
const faker = require('faker');
const ROUNDS = 100;
console.log(`Generating ${ROUNDS} sets of data...`);
const fixture = new Array(ROUNDS).fill(null).map(() => faker.helpers.userCard());
const suite = new Benchmark.Suite;
console.log(`Measurement...`)
suite
  .add('Reduce - left references', () => {
    fixture.reduce((acc, curr) => {
      const { city } = curr.address;
      return {
        ...acc,
        [city]: (acc[city] || 0) + 1
      }
    }, {});
  })
  .add('Reduce - save reference', () => {
    fixture.reduce((acc, curr) => {
      const { city } = curr.address;
      acc[city] = (acc[city] || 0) + 1;
      return acc;
    }, {});
  })
  // add listeners
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log(`Fastest is "${this.filter('fastest').map('name')}"`);
  })
  .run();

Результаты:

Generating 100 sets of data...
Measurement...
Reduce - left references x 4,357 ops/sec ±0.95% (91 runs sampled)
Reduce - save reference x 132,828 ops/sec ±1.10% (94 runs sampled)
Fastest is "Reduce - save reference"

Как видите, если мы сохраним ссылку, процессор сможет выполнять больше операций в секунду - это НА 30 РАЗ БЫСТРЕЕ! Хотите попробовать? Клонируйте мое репо и запустите npm run test:reduce