Библиотека Moment.js построена вокруг объекта moment,
который инкапсулирует дату и время и предоставляет богатый набор методов
для их преобразования. Ключевая особенность внутреннего поведения таких
объектов — изменяемость состояния (mutable behavior). Это означает, что
большинство операций над экземпляром moment не создают
новый объект, а изменяют текущий.
Такой подход исторически был выбран для удобства цепочек вызовов и сокращения количества временных объектов, однако он приводит к важным последствиям при проектировании логики работы с датами.
Экземпляр moment хранит внутреннее состояние даты и
времени, включая:
При вызове методов трансформации происходит модификация этих полей внутри одного и того же объекта.
Пример поведения:
const m = moment("2024-01-01");
m.add(7, "days");
console.log(m.format("YYYY-MM-DD")); // 2024-01-08
Метод add изменяет исходный объект m. Это
фундаментальное отличие от функционально-иммутабельных моделей работы с
датами.
Большая часть API Moment.js относится к категории mutating-методов. Они напрямую модифицируют текущий экземпляр.
addsubtractsetstartOfendOfutclocalzone (в некоторых конфигурациях)Пример цепочки вызовов:
const m = moment("2024-01-01");
m.add(1, "month")
.subtract(2, "days")
.startOf("day");
Все операции выполняются над одним объектом. Промежуточные состояния не сохраняются, если явно не клонировать объект.
Изменяемость становится критичной при передаче объектов между
функциями. Поскольку moment передаётся по ссылке, любая
функция может изменить исходное значение.
Пример побочного эффекта:
function shiftDate(date) {
date.add(3, "days");
}
const original = moment("2024-01-01");
shiftDate(original);
console.log(original.format("YYYY-MM-DD")); // 2024-01-04
Функция изменила внешний объект, хотя это не очевидно из сигнатуры.
Для предотвращения побочных эффектов используется метод
clone(). Он создаёт новый экземпляр с тем же
состоянием.
const original = moment("2024-01-01");
const copy = original.clone();
copy.add(10, "days");
console.log(original.format("YYYY-MM-DD")); // 2024-01-01
console.log(copy.format("YYYY-MM-DD")); // 2024-01-11
Клонирование является основным механизмом обеспечения логической неизменяемости при работе с Moment.js.
Иммутабельное поведение можно имитировать, всегда возвращая новый объект после операции.
const base = moment("2024-01-01");
const result = base.clone()
.add(5, "days")
.startOf("month")
.subtract(1, "hour");
Такой подход делает код предсказуемым и снижает вероятность скрытых изменений состояния.
Несмотря на общую мутабельность, некоторые методы возвращают новый экземпляр вместо изменения текущего.
К таким относятся:
moment() как конструкторclone() как явное копированиеmoment.utc() при создании нового объектаmoment.parseZone() в некоторых сценарияхОднако важно различать создание нового объекта и трансформацию существующего: большинство трансформаций всё равно остаются мутабельными.
Цепочный API создаёт иллюзию функционального стиля, но фактически изменяет один объект последовательно.
const m = moment("2024-01-01");
m.add(1, "day")
.add(1, "month")
.year(2030);
Результат — один объект с финальным состоянием. Промежуточные даты не сохраняются.
Это может приводить к ошибкам, если цепочка используется повторно:
const base = moment("2024-01-01");
const a = base.add(1, "day");
const b = base.add(1, "day");
console.log(a.format()); // уже изменённое значение
console.log(b.format()); // ещё более изменённое значение
Использование Moment.js в больших приложениях требует строгого контроля владения объектами.
Типовые проблемы:
Чистые функции с датами требуют обязательного клонирования входных значений:
function getNextWeek(date) {
return date.clone().add(7, "days");
}
Без клонирования:
const d = moment("2024-01-01");
const result = d.add(10, "days");
console.log(d === result); // true
С клонированием:
const d = moment("2024-01-01");
const result = d.clone().add(10, "days");
console.log(d === result); // false
В первом случае d и result — один объект.
Во втором — разные экземпляры.
Методы форматирования не изменяют объект, но используют его текущее состояние.
const m = moment("2024-01-01");
m.add(1, "day");
console.log(m.format("YYYY-MM-DD"));
Здесь format только читает состояние. Это важно для
разделения операций:
Архитектура Moment.js ориентирована на эпоху, когда:
Поэтому mutability стала компромиссом между удобством и предсказуемостью.
Для контроля состояния применяется несколько устойчивых подходов:
Пример изолированного использования:
function formatDeadline(date) {
const safe = date.clone();
return safe.add(2, "days").format("YYYY-MM-DD");
}
Каждое последующее изменение опирается на уже изменённое состояние:
const m = moment("2024-01-01");
m.add(1, "day"); // 2024-01-02
m.add(1, "day"); // 2024-01-03
m.add(1, "day"); // 2024-01-04
Отсутствие промежуточных копий делает результат линейно зависимым от порядка вызовов.
Поведение объекта Moment.js можно описать через три принципа:
Такая модель требует дисциплины в обращении с объектами, особенно в кодовых базах, где даты передаются между слоями приложения или используются в повторных вычислениях.