Особенности иммутабельных объектов

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

Такой подход принципиально отличает js-joda от стандартного Date в JavaScript, где методы часто изменяют внутреннее состояние объекта.


Базовый принцип неизменяемости

Каждый объект в js-joda рассматривается как value object — объект-значение. Его состояние фиксируется при создании и не подлежит изменению.

import { LocalDate } from '@js-joda/core';

const date1 = LocalDate.of(2024, 1, 10);
const date2 = date1.plusDays(5);

console.log(date1.toString()); // 2024-01-10
console.log(date2.toString()); // 2024-01-15

Ключевое поведение:

  • date1 не изменяется после вызова plusDays
  • date2 — новый объект
  • результат операции всегда детерминирован и не влияет на исходные данные

Операции как фабрики новых объектов

Любая модификация даты или времени в js-joda реализована через создание нового экземпляра. Это относится ко всем основным типам:

  • LocalDate
  • LocalTime
  • LocalDateTime
  • ZonedDateTime
  • Instant
const time = LocalTime.of(10, 30);
const updated = time.withHour(15);

console.log(time.toString());    // 10:30
console.log(updated.toString()); // 15:30

Методы plus*, minus*, with* не изменяют состояние, а возвращают новый объект с изменённым значением одного из компонентов.


Цепочки вызовов и функциональный стиль

Иммутабельность позволяет строить цепочки преобразований без риска побочных эффектов:

const result = LocalDate
  .of(2024, 1, 1)
  .plusMonths(1)
  .plusDays(10)
  .minusYears(1);

Каждый вызов:

  • создаёт промежуточный объект
  • не влияет на предыдущие значения
  • может безопасно использоваться в цепочке

Такой стиль делает код предсказуемым и легко тестируемым.


Безопасность состояния и отсутствие побочных эффектов

Иммутабельные объекты устраняют класс проблем, связанных с неожиданным изменением состояния:

function addWeek(date) {
  return date.plusWeeks(1);
}

const base = LocalDate.of(2024, 6, 1);
const modified = addWeek(base);

console.log(base.toString());     // 2024-06-01
console.log(modified.toString()); // 2024-06-08

Функция не может случайно изменить входной объект, что упрощает:

  • композицию функций
  • параллельное выполнение кода
  • повторное использование значений

Сравнение с изменяемыми объектами Date

Стандартный Date в JavaScript ведёт себя иначе:

const d = new Date(2024, 0, 1);
d.setDate(d.getDate() + 5);

Проблемы подхода Date:

  • изменение происходит внутри объекта
  • сложно отслеживать состояние
  • легко получить побочные эффекты при передаче ссылок

В js-joda аналогичная операция выглядит иначе:

const d = LocalDate.of(2024, 1, 1);
const newD = d.plusDays(5);

Разница принципиальная: вместо мутации используется трансформация.


Равенство и сравнение объектов

Из-за иммутабельности объекты js-joda можно безопасно сравнивать как значения.

Используется метод equals, а не оператор ==:

const a = LocalDate.of(2024, 1, 1);
const b = LocalDate.of(2024, 1, 1);

console.log(a.equals(b)); // true
console.log(a === b);     // false

Особенности:

  • === сравнивает ссылки
  • equals сравнивает значение
  • разные экземпляры могут быть равны по смыслу

Это соответствует концепции value object, где важны данные, а не идентичность объекта.


Безопасность при передаче между функциями

Иммутабельные объекты можно свободно передавать между слоями приложения без риска их изменения:

function format(date) {
  return date.plusDays(2).toString();
}

const original = LocalDate.of(2024, 3, 10);
const formatted = format(original);

console.log(original.toString()); // 2024-03-10

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


Внутренняя модель представления

Хотя объекты выглядят как обычные структуры, внутри js-joda реализует:

  • фиксированные числовые представления даты и времени
  • строгую нормализацию значений
  • отсутствие сеттеров
  • неизменяемые поля

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


Оптимизации и разделение экземпляров

Иммутабельность позволяет библиотеке использовать оптимизации:

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

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


Иммутабельность и производительность

На первый взгляд создание новых объектов может казаться затратным, однако в js-joda:

  • объекты лёгкие по структуре
  • операции оптимизированы под минимальное количество аллокаций
  • отсутствие синхронизации упрощает выполнение

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


Календарные вычисления как преобразования значений

Все операции над датами и временем трактуются как чистые функции:

  • вход: неизменяемый объект
  • выход: новый объект
const base = LocalDate.of(2024, 12, 31);

const nextYear = base.plusDays(1);

Такая модель делает поведение календарных вычислений предсказуемым даже в сложных сценариях с часовыми поясами и переходами дат.


Работа с временными зонами и неизменяемость

В объектах вроде ZonedDateTime временная зона также является частью неизменяемого состояния.

const zdt = ZonedDateTime.now();
const shifted = zdt.withZoneSameInstant(ZoneId.of('Europe/Paris'));

Особенность:

  • исходный объект сохраняет свою зону
  • новый объект содержит преобразованное представление
  • оба экземпляра остаются независимыми

Практическое значение иммутабельности

Иммутабельность в js-joda обеспечивает устойчивую модель данных, в которой:

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

Каждая дата или время рассматриваются как фиксированное значение, а все операции — как построение новых значений на основе исходных.