Во время отладки я нашел код, который брал объект, а затем возвращал измененную версию этого объекта. Справедливости ради, код мутировал состояние в редюсере, но это тема отдельного дня — по организационным причинам рефакторинг нужно подождать до ноября.

Проблема

Следующий базовый пример функционально подобен коду, с которым я столкнулся, и он работал так, как ожидалось:

Попытка использовать другой синтаксис клонирования объектов вызвала ошибки в другом месте нашего приложения…

Синтаксис распространения вызвал ту же ошибку:

Если вы не знакомы с синтаксисом распространения, используемым здесь, он должен возвращать неглубокий клон вышеуказанного объекта. Вот вопрос, на который мне нужно было ответить: в чем разница между передачей объекта в качестве первого аргумента Object.assign() и передачей того же объекта в качестве второго аргумента или использованием синтаксиса распространения?

Давайте углубимся в документацию. ДляObject.assign() первый аргумент называется целью, а последующие аргументы называются источниками.

Метод Object.assign() копирует только перечисляемые и собственные свойства из исходного объекта в целевой объект. Он использует [[Get]] для источника и [[Set]] для цели, поэтому он будет вызывать геттеры и сеттеры. Поэтому он присваивает свойства, а не просто копирует или определяет новые свойства. Это может сделать неподходящим для слияния новых свойств с прототипом, если источники слияния содержат геттеры.

Акцент добавлен — если вам нужна информация о геттерах (или знакомство с ними), см. Документацию по геттерам MDN. Предложение по синтаксису распространения этапа 4 описывает нечто подобное:

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

Давайте рассмотрим пример кода, демонстрирующий различия в отношении геттеров и сеттеров.

Изучение проблемы

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

Достаточно просто! Теперь давайте попробуем (поверхностно) клонировать этот объект, используя первый нерабочий пример. Первый:

Почему просмотр возвращает 3? Потому что свойства secondStack теперь выглядят так:

Почему? Помните, что в документации для Object.assign() говорится, что он использует [[Get]] для источника и [[Set]] для цели, поэтому для исходных объектов (например, второго аргумента и выше) вызываются геттеры и сеттеры вместо их копирования. Object.assign() заменил геттер peek() на результат геттера при назначении или 3.

Напоминание о том, что это неглубокий клон:

Вот как выглядел второй нерабочий пример:

Свойства thirdStack теперь выглядят так же, как Object.assign({}, stack):

В документации об этом ничего не сказано: она возвращает значение геттера при присваивании, а не сам геттер.

Демонстрация того же поведения с сеттерами

И новый пример!

Разница в поведении для сеттеров идентична. Я просто начну с нового примера, прямо со страницы установки MDN для простоты.

Давайте используем его в качестве исходного аргумента в Object.assign():

В документации сказано, что «Он использует [[Get]] для источника и [[Set]] для цели, поэтому он будет вызывать геттеры и сеттеры.» Насколько я могу судить, я бы перефразировал это как: объект возвращенный из Object.assign(), будет иметь геттеры и сеттеры целевого объекта и заменит геттеры и сеттеры исходных объектов значениями, возвращенными при вызове Object.assign().

Двигаясь дальше, если мы используем Object.assign() с нашим языковым объектом в качестве источника, вызывается сеттер:

И наконец:

Урок: даже поверхностное клонирование объектов в Javascript имеет свои нюансы.

Но мой личный вывод здесь связан с контекстом, в котором я впервые столкнулся с этой проблемой. Фактический объект состояния был массивным и глубоко вложенным, и поэтому наш редюсер был написан с учетом неправильного типа хранилища: это можно было бы смягчить, создав состояние, которое было бы глубоко клонировано синтаксисом распространения или Object.assign(), или структурировав хранилище таким образом. таким образом, чтобы глубокий клон было просто написать, сгладив и уменьшив размер.