Во время отладки я нашел код, который брал объект, а затем возвращал измененную версию этого объекта. Справедливости ради, код мутировал состояние в редюсере, но это тема отдельного дня — по организационным причинам рефакторинг нужно подождать до ноября.
Проблема
Следующий базовый пример функционально подобен коду, с которым я столкнулся, и он работал так, как ожидалось:
Попытка использовать другой синтаксис клонирования объектов вызвала ошибки в другом месте нашего приложения…
Синтаксис распространения вызвал ту же ошибку:
Если вы не знакомы с синтаксисом распространения, используемым здесь, он должен возвращать неглубокий клон вышеуказанного объекта. Вот вопрос, на который мне нужно было ответить: в чем разница между передачей объекта в качестве первого аргумента 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()
, или структурировав хранилище таким образом. таким образом, чтобы глубокий клон было просто написать, сгладив и уменьшив размер.