Для того, чтобы получить копию объекта, как сложного типа данных, придется приложить некоторое количество усилий, т.к. в отличие от примитивных типов Number, String, Boolean, которые чаще всего являются значениями свойств объекта, обычное присваивание не создаст нам второй объект с такими же точно данными. Давайте посмотрим, почему.

Способ 1. Создание дополнительной ссылки на объект

Начнем с примитивов, чтобы была видна разница.

Из примера видно, что каждая из числовых или строковых переменных, которые имели при присвоении (копировании) одно и то же значение, впоследствии изменяется абсолютно автономно. Обратите внимание, что, если значения этих типов равны, то равны и сами переменные.

Давайте теперь то же самое проделаем с объектом. То есть мы просто присвоим объект в другую переменную. А затем изменим одно свойство в исходном объекте и в его клоне, или копии.

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

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

Передача объекта по ссылке

Поэтому нельзя назвать копией вторую переменную. Это больше похоже на телефонную связь с одним абонентом. Номер его телефона может храниться в любом количестве других девайсов, но дозваниваться (ссылаться в случае объектов в JS) с них будут всегда на одно и то же устройство с этим номером. Поэтому слова "способ 1" в заголовке перечеркнуты, т.к. этот процесс не является копированием с получением 2-х разных объектов с идентичными свойствами и методами.

Есть еще один нюанс, касающийся сравнения объектов. Когда мы пишем строки вида obj1===obj2, то true вернется только в случае, рассмотренном выше, когда переменные ссылаются на один и тот же объект Если же объекты являются идентичными, но созданы вручную при написании кода или любым из перечисленных ниже способов, то сравнение на равенство вернет false, т.к. каждый объект имеет свой собственный адрес в памяти компьютера. И они не совпадают!

Оператор spread для копирования объектов

Способ 2. Быстрое копирование одноуровневого объекта оператором spread

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

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

Способ 3. Поверхностное копирование с помощью Object.assign()

Метод Object.assign() позволяет скопировать свойства одного объекта в другой. Этот метод предполагает, что мы передаем первым аргументом пустой объект, а вторым - тот объект, который копируем, или несколько таких объектов.

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

Цикл for...in или метод Object.keys() для копирования объектов в JS

Способ 4. Глубокое копирование объектов с помощью цикла for...in или Object.keys()

Допустим, нам нужно сделать точную копию - клон объекта, в котором одно или 2 свойства тоже являются объектами + есть один метод или два. Ни один из предыдущих способов не годится, т.к. первый - это не клонирование, а создание ссылки на уже существующий объект, второй просто не подходит, т.к. объект сложный, а в третьем копируются только свойства, но методы и свойства-объекты передаются как ссылки, т.е. как в способе 1.

Используем цикл for...in

В этом случае можно использовать цикл for...in для прохода по всем собственным свойствам и методам объекта (без свойств и методов прототипа), что реализуется использованием метода hasOwnProperty(key). Проще всего делать это с помощью функции, которую можно использовать столько раз, сколько вам нужно. В том случае, если свойство само является объектом (проверяем это с помощью строки typeof obj[key] === 'object'), то мы рекурсивно вызываем нашу функцию cloneSomeObject():

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

Сделаем проверку: изменим свойства в исходном и скопированном объекте и выведем, что получилось, используя метод showInfo():

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

Используем метод Object.keys()

Очень похожий вариант глубокого копирования объекта мы можем получить, используя метод Object.keys(). Здесь также мы проходим по свойствам объектов методом map() массивов. В каждой итерации map() проверяем свойства по одному на содержание в нем какого-либо объекта. Если да, то выполняется рекурсивный вызов той же самой функции deepCloneObject, и значением текущего свойства будет вложенный объект.

Снова проверяем копии на индивидуальность:

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

Подводные камни при копировании дат и массивов

Код 2-х функций выше предполагает, что мы копируем объекты с вложенными объектами. Однако вложенными объектами также могут быть массивы (экземпляры объекта Array) или даты (экземпляры объекта Date). И вот тут мы можем получить совсем не то, что планировали.

На скриншоте ниже можно увидеть, что в объекте-копии массив превратился в объект типа {}, а объект Date не имеет значения, а только лишь имеет прототип в виде стандартного класса Object:

Копия объекта со свойством в виде массива и датыПри проверке объектов путем изменения скопированного массива методом pop() и вывода метода showArrivalInfo(), использующего объект Date, мы получаем ошибки:

Давайте перепишем одну из предыдущих функций, используя дополнительные проверки с помощью условной конструкции if...else:

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

Как видно из кода, все работает, как и предполагалось. У нас теперь и в копии объекта есть массив и дата в виде объектов соответствующего типа, к которым можно применить характерные для них методы.

Копирование с помощью JSON

Способ 5. Использование JSON.stringify() и JSON.parse() для глубокого копирования объектов

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

Если в объекте отсутствуют функции, значения типа undefined, NaN или Date, то глубокое копирование такого объекта можно совершить с помощью JSON.stringify() и JSON.parse(). С помощью этих методов  мы последовательно превращаем наш объект сначала в строку, а затем снова в объект, но при этом начальный объект и результат наших манипуляций - это уже 2 разных объекта.

Тестируем объект, аналогичный предыдущему по своим свойствам и методам:

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

Копирование объекта с помощью JSON

Вывод: этот способ копирования нельзя использовать для копирования методов объекта, которые были записаны в нем самом, а не в его прототипе, а также объект Date.

Дополнительная проверка:

Проверка показала, что JSON.parse(JSON.stringify(obj)) не работает, если свойством объекта является функция, т.е. это уже не свойство, а метод объекта. Также нельзя скопировать значения undefined, NaN и Date, потому что они не подходят для синтаксиса формата JSON. Поэтому используйте этот способ, только когда объект содержит строки и числа, а также вложенные объекты с теми же типами данных и массивы.

Заключение

Копировать, или клонировать объекты можно разными способами в зависимости от того, какую цель вы преследуете. Далеко не всегда нужно глубокое копирование, поэтому для простых объектов подойдет способ с оператором расширения или методом Object.assign(). В зависимости от того, какие типы данных у вас присутствуют в ключах объекта, для глубокого копирования вы можете выбрать JSON.parse(JSON.stringify(obj)) или рекурсивный проход по свойствам и методам объекта с помощью цикла for...in или Object.keys() . Помните о том, что глубокое копирование объекта - это трудозатратная операция, поэтому выбирайте оптимальный способ в зависимости от задачи.

 

Автор: Админ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *