Клонирование объектов moment

Объекты библиотеки Moment.js являются изменяемыми. Это означает, что большинство методов не создают новый экземпляр даты, а изменяют существующий объект.

const date = moment();

date.add(1, 'day');

console.log(date.format());

Метод add() изменяет исходный объект date. Такое поведение отличается от многих современных библиотек, использующих неизменяемые структуры данных.

Из-за изменяемости объектов возникает распространённая ошибка: несколько переменных начинают ссылаться на один и тот же объект moment.

const original = moment();
const copy = original;

copy.add(7, 'days');

console.log(original.format());
console.log(copy.format());

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

Для создания независимого экземпляра используется клонирование.


Метод clone()

Основной способ клонирования — метод clone().

const original = moment();
const cloned = original.clone();

cloned.add(1, 'month');

console.log(original.format());
console.log(cloned.format());

После вызова clone() создаётся новый объект moment, содержащий:

  • ту же дату;
  • то же время;
  • ту же временную зону;
  • ту же локаль;
  • те же внутренние настройки.

При изменении клона оригинальный объект остаётся неизменным.


Как работает клонирование

Внутри библиотеки создаётся новый экземпляр moment с копированием состояния исходного объекта.

const first = moment('2025-01-10');
const second = first.clone();

console.log(first === second);

Результат:

false

Объекты разные, несмотря на одинаковое содержимое.


Клонирование через moment(existingMoment)

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

const original = moment();
const cloned = moment(original);

Передача объекта moment в конструктор moment() автоматически создаёт копию.

Это поведение эквивалентно:

const cloned = original.clone();

Сравнение способов клонирования

Использование clone()

const copy = original.clone();

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

  • явно показывает намерение;
  • легче читается;
  • считается стандартным способом.

Использование moment(existingMoment)

const copy = moment(original);

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

  • удобно при универсальном коде;
  • полезно в функциях, принимающих разные типы данных.

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

Ошибка при вычислениях

const start = moment('2025-05-01');

const nextWeek = start.add(7, 'days');

console.log(start.format('YYYY-MM-DD'));

Результат:

2025-05-08

Исходная дата была изменена.

Правильный вариант:

const start = moment('2025-05-01');

const nextWeek = start.clone().add(7, 'days');

console.log(start.format('YYYY-MM-DD'));
console.log(nextWeek.format('YYYY-MM-DD'));

Результат:

2025-05-01
2025-05-08

Клонирование перед изменением даты

Практически любой метод модификации изменяет объект:

  • add()
  • subtract()
  • startOf()
  • endOf()
  • set()
  • year()
  • month()
  • date()
  • hour()
  • minute()

Поэтому распространённый шаблон выглядит так:

const result = date.clone().add(1, 'day');

Клонирование и методы startOf() / endOf()

Методы начала и конца периода особенно часто вызывают побочные эффекты.

Без клонирования

const now = moment();

const startDay = now.startOf('day');

console.log(now.format());

now уже изменён.

С клонированием

const now = moment();

const startDay = now.clone().startOf('day');

console.log(now.format());
console.log(startDay.format());

Клонирование в цепочках методов

Moment.js поддерживает chaining — цепочки вызовов.

const result = moment()
    .clone()
    .add(1, 'week')
    .startOf('week')
    .hour(9);

Клон создаётся один раз, после чего безопасно выполняются модификации.


Клонирование объектов с временными зонами

При использовании плагина Moment Timezone временная зона также копируется.

const moscow = moment.tz(
    '2025-03-10 12:00',
    'Europe/Moscow'
);

const cloned = moscow.clone();

console.log(cloned.tz());

Результат:

Europe/Moscow

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

Локаль тоже переносится в новый объект.

const date = moment()
    .locale('ru');

const cloned = date.clone();

console.log(cloned.locale());

Результат:

ru

Клонирование UTC-объектов

UTC-состояние сохраняется.

const utcDate = moment.utc();

const cloned = utcDate.clone();

console.log(cloned.isUTC());

Результат:

true

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

После клонирования значения одинаковы, но ссылки различаются.

const a = moment();
const b = a.clone();

console.log(a.isSame(b));
console.log(a === b);

Результат:

true
false
  • isSame() сравнивает дату и время;
  • === сравнивает ссылки на объект.

Поверхностное копирование через JavaScript

Обычные способы копирования объектов JavaScript не подходят.

Ошибочный вариант

const copy = { ...moment() };

Moment.js содержит внутренние структуры и методы, которые не копируются корректно.

Аналогично не работают:

Object.assign({}, moment());

или

JSON.parse(JSON.stringify(moment()));

Эти методы создают обычные объекты, а не полноценные экземпляры moment.


Клонирование массивов объектов moment

При работе с массивами требуется клонировать каждый элемент отдельно.

Ошибка

const dates = [
    moment(),
    moment().add(1, 'day')
];

const copied = [...dates];

copied[0].add(1, 'month');

Изменится и оригинальный массив.

Правильный вариант

const copied = dates.map(date => date.clone());

Клонирование в функциях

Опасный вариант

function addWeek(date) {
    date.add(1, 'week');

    return date;
}

Функция изменяет переданный объект.

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

function addWeek(date) {
    return date.clone().add(1, 'week');
}

Такой подход уменьшает количество скрытых побочных эффектов.


Клонирование перед сериализацией

Иногда требуется временно изменить дату для форматирования.

const date = moment();

const formatted = date
    .clone()
    .utc()
    .format();

Исходный объект остаётся в локальном времени.


Использование клонирования в вычислениях диапазонов

Пример календарного диапазона

const current = moment();

const startMonth = current
    .clone()
    .startOf('month');

const endMonth = current
    .clone()
    .endOf('month');

Без clone() объект current был бы изменён несколько раз подряд.


Клонирование в циклах

Генерация списка дат

const start = moment('2025-01-01');
const dates = [];

for (let i = 0; i < 5; i++) {
    dates.push(
        start.clone().add(i, 'days')
    );
}

Каждый элемент массива является независимым объектом.


Распространённая ошибка с повторным использованием объекта

const day = moment();

const monday = day.startOf('week');
const friday = day.endOf('week');

После вызова endOf() объект monday тоже изменится, поскольку обе переменные указывают на один экземпляр.

Правильный вариант:

const day = moment();

const monday = day
    .clone()
    .startOf('week');

const friday = day
    .clone()
    .endOf('week');

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

Операция clone() достаточно лёгкая, однако при огромном количестве объектов могут появляться накладные расходы.

Например:

for (let i = 0; i < 100000; i++) {
    moment().clone();
}

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


Когда клонирование обязательно

Клонирование требуется практически всегда, если:

  • объект moment передаётся между функциями;
  • дата используется повторно;
  • выполняются вычисления диапазонов;
  • используется chaining с модификацией;
  • объект хранится в состоянии приложения;
  • один экземпляр участвует в нескольких вычислениях одновременно.

Когда клонирование можно не использовать

Клонирование необязательно, если объект больше нигде не используется.

const result = moment()
    .add(1, 'day')
    .format();

Исходный объект нигде не сохраняется, поэтому изменение безопасно.


Типичный безопасный стиль работы

Распространённый подход:

const baseDate = moment();

const tomorrow = baseDate
    .clone()
    .add(1, 'day');

const nextMonth = baseDate
    .clone()
    .add(1, 'month');

const endYear = baseDate
    .clone()
    .endOf('year');

Исходная дата остаётся неизменной независимо от количества вычислений.


Ключевые особенности клонирования

Особенность Описание
Создаёт новый объект Да
Копирует дату и время Да
Копирует локаль Да
Копирует timezone Да
Копирует UTC-состояние Да
Изменения влияют на оригинал Нет
Основной метод clone()

Основные правила безопасной работы

Не присваивать объект moment напрямую

const b = a;

Это копирование ссылки.

Клонировать перед модификацией

a.clone().add(1, 'day');

Не использовать spread и Object.assign

{ ...moment() }

Создаёт некорректную копию.

Делать функции чистыми

return date.clone().add(1, 'week');

Функция не должна менять входные данные без необходимости.