Кеширование и переиспользование объектов

Библиотека Day.js строит работу вокруг неизменяемых (immutable) объектов даты-времени. Каждый вызов модифицирующей операции не изменяет исходный экземпляр, а возвращает новый объект. Это напрямую влияет на стратегию кеширования и переиспользования: «обновление» состояния происходит через создание новых экземпляров, а не через мутацию.

Ключевое свойство:

  • Day.js объект не изменяется после создания
  • Любая операция (add, subtract, set) возвращает новый экземпляр
  • Исходные данные остаются стабильными
import dayjs fr om 'dayjs';

const a = dayjs('2026-01-01');
const b = a.add(1, 'day');

console.log(a.format()); // 2026-01-01
console.log(b.format()); // 2026-01-02

Это поведение определяет базовую стратегию оптимизации: кеширование должно строиться вокруг входных данных, а не попыток «обновлять» объекты.


Стоимость создания объектов Day.js

Создание экземпляра Day.js включает несколько этапов:

  • парсинг входной строки или timestamp
  • нормализация времени
  • применение локали (если подключена)
  • обработка плагинов (UTC, timezone и др.)

На практике основная нагрузка возникает при:

  • массовом парсинге строк дат
  • повторных вызовах dayjs() в циклах
  • форматировании одних и тех же значений
for (let i = 0; i < 100000; i++) {
  dayjs('2026-01-01').format('YYYY-MM-DD');
}

В подобных сценариях узким местом становится не форматирование, а повторное создание идентичных объектов.


Базовый принцип кеширования: избегание повторного парсинга

Самый эффективный уровень оптимизации — устранение повторного создания объектов из одинаковых входных данных.

Кеширование по строковому ключу

const cache = new Map();

function getDayjs(dateString) {
  if (cache.has(dateString)) {
    return cache.get(dateString);
  }

  const value = dayjs(dateString);
  cache.set(dateString, value);

  return value;
}

Особенность: кешируются уже готовые объекты Day.js.

Проблема такого подхода:

  • объекты могут устаревать в контексте «динамического времени»
  • возможен рост памяти при большом числе уникальных ключей

Кеширование по timestamp вместо строк

Строки дат могут быть неоднозначными (форматы, локали), тогда как timestamp стабилен.

const cache = new Map();

function getDayjsFromTs(ts) {
  let cached = cache.get(ts);

  if (!cached) {
    cached = dayjs(ts);
    cache.set(ts, cached);
  }

  return cached;
}

Преимущество:

  • унификация ключа
  • снижение риска дубликатов

Ограничения кеширования объектов Day.js

Несмотря на удобство, кеширование экземпляров имеет ограничения:

1. Неизменяемость не равна актуальности

Если закеширован момент времени, он не «обновится»:

const now = dayjs();

setTimeout(() => {
  console.log(now.format()); // время создания, не текущее
}, 5000);

Поэтому кеширование now недопустимо, если требуется текущее время.


2. Контекст времени (timezone, locale)

Один и тот же timestamp может интерпретироваться по-разному:

  • UTC
  • локальное время
  • timezone plugin

Кеш без учёта контекста может возвращать некорректные данные.

const cache = new Map();

function getDate(ts, tz) {
  const key = `${ts}_${tz}`;
  if (!cache.has(key)) {
    cache.set(key, dayjs(ts).tz(tz));
  }
  return cache.get(key);
}

Кеширование форматированных строк

Частая операция — повторное форматирование одной и той же даты.

Подход: кеш форматирования

const formatCache = new Map();

function formatDate(ts, format) {
  const key = `${ts}_${format}`;

  if (formatCache.has(key)) {
    return formatCache.get(key);
  }

  const result = dayjs(ts).format(format);
  formatCache.set(key, result);

  return result;
}

Это особенно эффективно при:

  • таблицах данных
  • UI списках
  • логах

Переиспользование объектов вместо повторного создания

В ряде случаев лучше не кешировать, а переиспользовать ссылку на уже созданный объект.

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

Плохой подход:

const result = arr.map(x => dayjs(x).format('YYYY-MM-DD'));

Если данные повторяются:

const cache = new Map();

const result = arr.map(x => {
  let d = cache.get(x);

  if (!d) {
    d = dayjs(x);
    cache.set(x, d);
  }

  return d.format('YYYY-MM-DD');
});

Кеширование цепочек преобразований

Day.js часто используется в цепочках:

dayjs('2026-01-01').add(3, 'day').subtract(1, 'month').format();

Повторное выполнение таких цепочек может быть дорогим.

Стратегия: кеширование промежуточного результата

const chainCache = new Map();

function getProcessedDate(input) {
  if (chainCache.has(input)) {
    return chainCache.get(input);
  }

  const value = dayjs(input)
    .add(3, 'day')
    .subtract(1, 'month');

  chainCache.set(input, value);

  return value;
}

Ограничение роста кеша

Без контроля кеш может стать причиной утечек памяти.

LRU-подход

class LRUCache {
  constructor(lim it = 1000) {
    this.limit = limit;
    this.map = new Map();
  }

  get(key) {
    const value = this.map.get(key);
    if (!value) return null;

    this.map.delete(key);
    this.map.set(key, value);

    return value;
  }

  set(key, value) {
    if (this.map.has(key)) {
      this.map.delete(key);
    }

    this.map.set(key, value);

    if (this.map.size > this.limit) {
      const firstKey = this.map.keys().next().value;
      this.map.delete(firstKey);
    }
  }
}

Использование:

const cache = new LRUCache(500);

function getDayjs(ts) {
  let value = cache.get(ts);

  if (!value) {
    value = dayjs(ts);
    cache.set(ts, value);
  }

  return value;
}

Кеширование с учётом плагинов

Day.js расширяется через плагины (utc, timezone, customParseFormat). Это влияет на корректность кеширования.

Пример с UTC

function getUTCDate(ts) {
  return dayjs(ts).utc();
}

Если кешировать без учёта .utc(), возможны ошибки:

const cache = new Map();

function getUTC(ts) {
  const key = ts + '_utc';

  if (!cache.has(key)) {
    cache.set(key, dayjs(ts).utc());
  }

  return cache.get(key);
}

Кеширование локалей

Локализация влияет на форматирование и разбор дат.

import 'dayjs/locale/ru';
import dayjs from 'dayjs';

dayjs.locale('ru');

Если в приложении переключаются локали, кеш должен учитывать их:

function formatLocalized(ts, format, locale) {
  const key = `${ts}_${format}_${locale}`;

  return cache.get(key) ?? (() => {
    const value = dayjs(ts).locale(locale).format(format);
    cache.set(key, value);
    return value;
  })();
}

Переиспользование вместо кеширования

Иногда кеширование избыточно, и эффективнее просто уменьшить количество созданий объектов.

Использование timestamp вместо Day.js объекта

const ts = Date.now();

// позже
dayjs(ts).format();

Это уменьшает необходимость хранения промежуточных объектов.


Паттерн: фабрика дат

Централизация создания объектов снижает дублирование логики и упрощает кеширование.

function createDate(input) {
  return dayjs(input);
}

Расширенная версия с кешем:

const cache = new Map();

function createDate(input) {
  if (cache.has(input)) {
    return cache.get(input);
  }

  const value = dayjs(input);
  cache.set(input, value);

  return value;
}

Типичные ошибки кеширования

Кеширование dayjs() без аргументов

const now = dayjs();

Ошибка: значение фиксируется в момент создания.


Кеширование mutable логики поверх immutable объекта

Несмотря на неизменяемость Day.js, кеш может стать источником логических ошибок при смешивании разных контекстов:

  • разные timezone
  • разные локали
  • разные форматы входных данных

Отсутствие контроля размера кеша

Простейший Map без ограничения приводит к:

  • росту памяти
  • деградации производительности GC

Практическая модель кеширования для Day.js

Комбинированный подход:

  • кешировать только детерминированные входы
  • учитывать timezone и locale
  • ограничивать размер кеша
  • разделять кеш даты и кеш форматирования
const dateCache = new Map();
const formatCache = new Map();

function getDate(ts, tz) {
  const key = `${ts}_${tz}`;

  if (!dateCache.has(key)) {
    const value = tz ? dayjs(ts).tz(tz) : dayjs(ts);
    dateCache.set(key, value);
  }

  return dateCache.get(key);
}

function formatDate(ts, format, tz) {
  const key = `${ts}_${format}_${tz}`;

  if (formatCache.has(key)) {
    return formatCache.get(key);
  }

  const value = getDate(ts, tz).format(format);
  formatCache.set(key, value);

  return value;
}