Копирование объектов и ссылки
Одно из фундаментальных отличий объектов от примитивов заключается в том, что объекты хранятся и копируются «по ссылке», тогда как примитивные значения: строки, числа, логические значения и т.д. – всегда копируются «как целое значение».
Это легко понять, если мы немного заглянем под капот того, что происходит, когда мы копируем значение.
Давайте начнём с примитива, такого как строка.
Здесь мы помещаем копию message во phrase :
В результате мы имеем две независимые переменные, каждая из которых хранит строку "Привет!" .
Вполне очевидный результат, не так ли?
Объекты ведут себя иначе.
Переменная, которой присвоен объект, хранит не сам объект, а его «адрес в памяти» – другими словами, «ссылку» на него.
Давайте рассмотрим пример такой переменной:
И вот как это на самом деле хранится в памяти:
Объект хранится где-то в памяти (справа от изображения), в то время как переменная user (слева) имеет лишь «ссылку» на него.
Мы можем думать о переменной объекта, такой как user , как о листе бумаги с адресом объекта на нем.
Когда мы выполняем действия с объектом, к примеру, берём свойство user.name , движок JavaScript просматривает то, что находится по этому адресу, и выполняет операцию с самим объектом.
Теперь вот почему это важно.
При копировании переменной объекта копируется ссылка, но сам объект не дублируется.
Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект:
Как вы можете видеть, все ещё есть один объект, но теперь с двумя переменными, которые ссылаются на него.
Мы можем использовать любую переменную для доступа к объекту и изменения его содержимого:
Это как если бы у нас был шкафчик с двумя ключами, и мы использовали один из них ( admin ), чтобы войти в него и внести изменения. А затем, если мы позже используем другой ключ ( user ), мы все равно открываем тот же шкафчик и можем получить доступ к изменённому содержимому.
Сравнение по ссылке
Два объекта равны только в том случае, если это один и тот же объект.
Например, здесь a и b ссылаются на один и тот же объект, поэтому они равны:
И здесь два независимых объекта не равны, даже если они выглядят одинаково (оба пусты):
Для сравнений типа obj1 > obj2 или для сравнения с примитивом obj == 5 объекты преобразуются в примитивы. Очень скоро мы изучим, как работают преобразования объектов, но, по правде говоря, такие сравнения требуются очень редко и обычно они появляются в результате ошибок программиста.
Клонирование и объединение, Object.assign
Итак, копирование объектной переменной создаёт ещё одну ссылку на тот же объект.
Но что, если нам всё же нужно дублировать объект? Создать независимую копию, клон?
Это тоже выполнимо, но немного сложнее, потому что в JavaScript для этого нет встроенного метода. Но на самом деле в этом редко возникает необходимость, копирования по ссылке в большинстве случаев вполне хватает.
Но если мы действительно этого хотим, то нам нужно создать новый объект и воспроизвести структуру существующего, перебрав его свойства и скопировав их на примитивном уровне.
Также мы можем использовать для этого метод Object.assign.
- Первый аргумент dest — целевой объект.
- Остальные аргументы src1, . srcN (может быть столько, сколько необходимо) являются исходными объектами
- Метод копирует свойства всех исходных объектов src1, . srcN в целевой объект dest . Другими словами, свойства всех аргументов, начиная со второго, копируются в первый объект.
- Возвращает объект dest .
Например, мы можем использовать его для объединения нескольких объектов в один:
Если скопированное имя свойства уже существует, оно будет перезаписано:
Мы также можем использовать Object.assign для замены цикла for..in для простого клонирования:
Он копирует все свойства user в пустой объект и возвращает его.
Также существуют и другие методы клонирования объекта. Например, с использованием оператора расширения clone = <. user>, рассмотренного далее в учебнике.
Вложенное клонирование
До сих пор мы предполагали, что все свойства user примитивныe. Но свойства могут быть и ссылками на другие объекты. Что с ними делать?
Например, есть объект:
Теперь недостаточно просто скопировать clone.sizes = user.sizes , потому что user.sizes – это объект, он будет скопирован по ссылке. Таким образом, clone и user будут иметь общий объект sizes :
Чтобы исправить это, мы должны использовать цикл клонирования, который проверяет каждое значение user[key] и, если это объект, тогда также копирует его структуру. Это называется «глубоким клонированием».
Мы можем реализовать глубокое клонирование, используя рекурсию. Или, чтобы не изобретать велосипед заново, возьмите готовую реализацию, например _.cloneDeep(obj) из библиотеки JavaScript lodash.
Также мы можем использовать глобальный метод structuredClone(), который позволяет сделать полную копию объекта. К сожалению он поддерживается только современными браузерами. Здесь можно ознакомиться с поддержкой этого метода.
Важным побочным эффектом хранения объектов в качестве ссылок является то, что объект, объявленный как const , может быть изменён.
Может показаться, что строка (*) вызовет ошибку, но, это не так. Значение user это константа, оно всегда должно ссылаться на один и тот же объект, но свойства этого объекта могут свободно изменяться.
Другими словами, const user выдаст ошибку только в том случае, если мы попытаемся задать user=. в целом.
Тем не менее, если нам действительно нужно создать постоянные свойства объекта, это тоже возможно, но с использованием совершенно других методов. Мы затронем это в главе Флаги и дескрипторы свойств.
Итого
Объекты присваиваются и копируются по ссылке. Другими словами, переменная хранит не «значение объекта», а «ссылку» (адрес в памяти) на это значение. Таким образом, копирование такой переменной или передача её в качестве аргумента функции копирует эту ссылку, а не сам объект.
Все операции с использованием скопированных ссылок (например, добавление/удаление свойств) выполняются с одним и тем же объектом.
Как копировать объект в JavaScript?
keslo: скажите, что именно не понятно — я отвечу.
Кстати, если интересует также и клонирование вложенных объектов (любого уровня вложенности), могу дополнить код.

Виталий Инчин ☢: но почему в функцию конструктор Create вы передаете «i» в качестве параметра? Выходит в конструктор можно ничего не передавать в данном случае?
И расскажите что делает код:
«__proto__» in o

keslo: фишка именно в том, что я не Передаю ее, а только объявляю. Но делаю я это не с помощью ключевого слова var, а с помощью аргуметов.
> И расскажите что делает код:
Проверяет, содержит ли объект свойство __proto__, которое указывает на его прототип. IE с этим свойством работать не умеет, для того и проверка.
Виталий Инчин ☢: объявление переменной действительно фишка! )
Строка copyProto && «__proto__» in o :
— copyProto при вызове clone необходимо передать, например, в значение true, чтобы скопировать прототип
— «__proto__» in o вернет при IE true
— и тогда мы скопируем ссылку на прототип на клонированный объект.
3 способа клонирования объектов в JavaScript
Поскольку объекты в #JavaScript являются ссылочными значениями, их нельзя просто скопировать с помощью = . Но не беспокойтесь, существует 3 способа клонирования объекта .
Объекты — это ссылочные типы
Почему нельзя использовать = ? Посмотрим, что может произойти:
Оба объекта выдают одно и то же. На данный момент никаких проблем. Рассмотрим, что произойдет после редактирования второго объекта:
obj2 был изменен, однако изменения коснулись и obj . Причина заключается в том, что объекты являются ссылочными типами. Поэтому при использовании = , указатель копируется в область занимаемой памяти. Ссылочные типы не содержат значений, они являются указателем на значение в памяти.
Использование Spread
С помощью spread можно клонировать объект. Обратите внимание, что копия будет неглубокой. На момент публикации этого руководства оператор spread для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).
Использование Object.assign
Object.assign , выпущенный официально, также создает неглубокую копию объекта.
Использование JSON
Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.
Lodash DeepClone или JSON?
- JSON.stringify/parse работает только с литералом Number, String и Object без функции или свойства Symbol.
- deepClone работает со всеми типами, а функция и символ копируются по ссылке.
Глубокое или неглубокое клонирование?
При использовании spread для копирования объекта создается неглубокая копия. Если массив является вложенным или многомерным, этот способ не будет работать. Рассмотрим пример:
Таким образом, клонированный объект был изменен с добавлением city. В результате получаем:
Неглубокая копия предполагает копирование первого уровня и ссылается на более глубокие уровни.
Глубокая копия
Возьмем тот же пример, но применим глубокую копию с использованием JSON:
Глубокая копия является копией для вложенных объектов. Однако иногда достаточно использования неглубокой копии.
Производительность
К сожалению, на данный момент нельзя написать тестирование для spread, поскольку официально он не указан в спецификации. Однако результат показывает, что Object.assign намного быстрее, чем JSON . Тест на производительность можно найти здесь.
Object.assign и Spread
Стоит отметить, что Object.assign — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:
<> — это модифицируемый объект. В этой точке на целевой объект не ссылаются никакие переменные, но поскольку Object.assign возвращает целевой объект, то можно сохранить полученный присвоенный объект в переменную cloneFood . Данный пример можно изменить следующим образом:
Очевидно, что значение beef в объекте food неверно, поэтому нужно назначить правильное значение beef с помощью Object.assign . На самом деле мы не используем возвращаемое значение функции, а изменяем целевой объект, на который ссылаемся с помощью константы food .
С другой стороны, Spread — это оператор, который копирует свойства одного объекта в новый объект. При репликации приведенного выше примера с помощью spread для изменения переменной food.
. мы получаем ошибку в результате использования spread для создания новых объектов и, следовательно, присваиваем новый объект для food , который был объявлен с помощью const , что недопустимо. Поэтому можно либо объявить новую переменную для хранения нового объекта, как показано ниже:
Object.assign()
Метод Object.assign() копирует все перечисляемые собственные свойства из одного или нескольких исходных объектов в целевой объект . Он возвращает измененный целевой объект.
Try it
Syntax
Parameters
Целевой объект-то,к чему следует применить свойства источников,который возвращается после его изменения.
Исходный объект(ы)-объекты,содержащие свойства,которые вы хотите применить.
Return value
Description
Свойства в целевом объекте перезаписываются свойствами в источниках, если они имеют одинаковый ключ . Свойства более поздних источников перезаписывают более ранние.
Метод Object.assign() копирует только перечисляемые и собственные свойства из исходного объекта в целевой объект. Он использует [[Get]] для источника и [[Set]] для цели, поэтому он будет вызывать геттеры и сеттеры . Поэтому он присваивает свойства, а не копирует или определяет новые свойства. Это может сделать его непригодным для слияния новых свойств в прототип, если источники слияния содержат геттеры.
Для копирования определений свойств (включая их перечислимость) в прототипы Object.getOwnPropertyDescriptor() используйте Object.getOwnPropertyDescriptor () и Object.defineProperty() .
Оба String и Symbol свойства копируются.
В случае ошибки, например, если свойство недоступно для записи, возникает ошибка TypeError , и target объект изменяется, если какие-либо свойства добавляются до возникновения ошибки.
Примечание. Object.assign() не выдает null или undefined источники.
Examples
Клонирование объекта
Предупреждение о глубоком клонировании
Для глубокого клонирования нам нужно использовать альтернативы, потому что Object.assign() копирует значения свойств.
Если исходное значение является ссылкой на объект,то копируется только ссылочное значение.
Merging objects
Слияние объектов с одинаковыми свойствами
Свойства перезаписываются другими объектами,имеющими те же свойства,позже в порядке параметров.