Привет Земляне!

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

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

Копирование объектов в Javascript

У нас есть несколько способов копировать объекты в javascript. Некоторые из них заключаются в следующем.

  • Оператор спреда (…)
  • Объект.назначить()
  • Сериализация JSON (JSON.stringify()), за которой следует JSON.parse()

Я буду использовать typescript для разработки этих методов.

Случай использования

Мы попробуем изменить свойства копий объектов и посмотрим, какое поведение мы увидим.

Типы.ts

Все наши типы будут содержаться в файле types.ts

метод сравнения объектов

Мы будем использовать следующий метод, чтобы проверить, равны ли объекты или нет. Он вернет true, если объекты равны, и вернет false, если объекты не равны.

Примечание. Этот метод предназначен для сравнения небольшого количества объектов, поскольку использование сериализации JSON может быть очень затратным для большого количества сложных объектов. Я рекомендую использовать ._Equal из библиотеки lodash. Тем не менее, это хорошо вписывается в наш сценарий.

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

Неглубокая копия (полевое копирование)

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

Глубокое копирование

Процесс создания нового составного объекта с последующим заполнением его копиями объектов, найденных в оригинале.

Сравнение глубокого и поверхностного копирования

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

Большое спасибо, что были со мной до сих пор! Я очень ценю это. теперь давайте перейдем к самому интересному

Концепция 1 (изменение свойств примитивов)

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

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

Мы видим, что наши примитивные значения изменяются во всех скопированных версиях объекта person. Наш метод compareObjects также проверяет, не равны ли эти объекты. Это означает, что если значение поля имеет тип примитивный, то копируемый объект также будет копировать значение примитивного типа.

Концепция № 2 (Изменение свойства объекта типа)

Давайте сбросим все клонированные объекты и пока изменим только свойство address объекта.

Мы видим, что значение, измененное в последнем скопированном объекте, сохраняется во всех объектах. Более того, наш метод compareObjects также проверяет, равны ли все объекты.

Что вы думаете об этом результате?

Если хорошенько подумать, то можно прийти к выводу, что Spread Operator и Object.assign() предоставили нам копии объектов со ссылкой на дочерние объекты, которые присутствовали в исходном варианте. Это означает, что мы сознательно или неосознанно выполнили поверхностное копирование. В результате изменение одного из них вызвало изменение свойства address всех объектов.

Что, если бы мы не хотели видеть такое поведение, то каким могло бы быть наше решение?

Решение

Мы будем глубоко копировать наш объект.

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

Способ 1 (сериализация JSON)

Сначала мы сериализуем наш объект с помощью JSON.stringify(). Это даст нам формат JSON нашего объекта, затем мы преобразуем его обратно в отдельный объект, используя JSON.parse().

Примечание. Следует помнить, что этот метод всегда будет возвращать дату в формате ISO с типом string.

Способ 2 (пользовательская функция)

Мы можем сделать нашу рекурсивную функцию для глубокого клонирования объекта следующим образом:

Тест

Мы создали объект clone с помощью сериализации JSON и clone_ с пользовательской функцией. Мы ожидаем, что эти оба объекта будут независимыми.

После изменения значения дочернего объекта address. Получаем следующие результаты

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

Мы также можем проверить это с помощью нашего метода compareObjects.

Заключение

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

Глубокое копирование по производительности:ранжировано от лучшего к худшему:

  • Оператор распространения ... (только примитивные массивы/объекты)
  • splice(0) (только примитивные массивы)
  • slice() (только примитивные массивы)
  • concat() (только примитивные массивы)
  • Пользовательская функция, как показано выше (любой массив/объект)
  • JQuery $.extend() (любой массив/объект)
  • JSON.parse(JSON.stringify()) (только примитивные и литеральные массивы/объекты)
  • Символ подчеркивания _.clone() (только примитивные и литеральные массивы/объекты)
  • Lodash’s _.cloneDeep() (любой массив/объект)

Где:

  • примитивы = строки, числа и логические значения
  • литералы = литералы объекта {}, литералы массива []
  • any = примитивы, литералы и прототипы

Q/A

Если что-то не работает должным образом или нуждается в дополнительной информации, просто оставьте комментарий ниже 😎 Удачного кодирования!