How to clone js object
How to clone js object
Копирование объектов и ссылки
Одним из фундаментальных отличий объектов от примитивных типов данных является то, что они хранятся и копируются «по ссылке».
Примитивные типы: строки, числа, логические значения – присваиваются и копируются «по значению».
Объекты ведут себя иначе.
Переменная хранит не сам объект, а его «адрес в памяти», другими словами «ссылку» на него.
Сам объект хранится где-то в памяти. А в переменной user лежит «ссылка» на эту область памяти.
Когда переменная объекта копируется – копируется ссылка, сам же объект не дублируется.
Если мы представляем объект как ящик, то переменная – это ключ к нему. Копирование переменной дублирует ключ, но не сам ящик.
Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект:
Мы можем использовать любую из переменных для доступа к ящику и изменения его содержимого:
Приведённый выше пример демонстрирует, что объект только один. Как если бы у нас был один ящик с двумя ключами и мы использовали один из них ( admin ), чтобы войти в него и что-то изменить, а затем, открыв ящик другим ключом ( user ), мы бы увидели эти изменения.
Сравнение по ссылке
Операторы равенства == и строгого равенства === для объектов работают одинаково.
Два объекта равны только в том случае, если это один и тот же объект.
В примере ниже две переменные ссылаются на один и тот же объект, поэтому они равны друг другу:
В другом примере два разных объекта не равны, хотя оба пусты:
Для сравнений типа obj1 > obj2 или для сравнения с примитивом obj == 5 объекты преобразуются в примитивы. Мы скоро изучим, как работают такие преобразования объектов, но, по правде говоря, сравнения такого рода необходимы очень редко и обычно являются результатом ошибки программиста.
Клонирование и объединение объектов, Object.assign
Таким образом, при копировании переменной с объектом создаётся ещё одна ссылка на тот же самый объект.
Но что, если нам всё же нужно дублировать объект? Создать независимую копию, клон?
Это выполнимо, но немного сложно, так как в JavaScript нет встроенного метода для этого. На самом деле, такая нужда возникает редко. В большинстве случаев нам достаточно копирования по ссылке.
Но если мы действительно этого хотим, то нам нужно создавать новый объект и повторять структуру дублируемого объекта, перебирая его свойства и копируя их.
Кроме того, для этих целей мы можем использовать метод Object.assign.
Например, объединим несколько объектов в один:
Если принимающий объект ( user ) уже имеет свойство с таким именем, оно будет перезаписано:
Мы также можем использовать Object.assign для замены for..in на простое клонирование:
Этот метод скопирует все свойства объекта user в пустой объект и возвратит его.
Вложенное клонирование
До сих пор мы предполагали, что все свойства объекта user хранят примитивные значения. Но свойства могут быть ссылками на другие объекты. Что с ними делать?
Например, есть объект:
Чтобы исправить это, мы должны в цикле клонирования делать проверку, не является ли значение userHow to clone js object объектом, и если это так – скопировать и его структуру тоже. Это называется «глубокое клонирование».
Мы можем реализовать глубокое клонирование, используя рекурсию. Или, чтобы не изобретать велосипед, использовать готовую реализацию — метод _.cloneDeep(obj) из JavaScript-библиотеки lodash.
Итого
Объекты присваиваются и копируются по ссылке. Другими словами, переменная хранит не «значение объекта», а «ссылку» (адрес в памяти) на это значение. Поэтому копирование такой переменной или передача её в качестве аргумента функции приводит к копированию этой ссылки, а не самого объекта.
Все операции с использованием скопированных ссылок (например, добавление или удаление свойств) выполняются с одним и тем же объектом.
# 3 Ways to Clone Objects in JavaScript
# Objects are Reference Types
So far, both object seems to output the same thing. So no problem, right. But let’s see what happens if we edit our second object:
If you want to learn more about this, check out Gordon’s Zhu Watch and Code
course. It’s free to enroll and watch the video «Comparison with objects». He gives a super awesome explanation on it.
# 1. Using Spread
Using spread will clone your object. Note this will be a shallow copy. As of this post, the spread operator for cloning objects is in Stage 4. So it’s not officially in the specifications yet. So if you were to use this, you would need to compile it with Babel (or something similar).
# 2. Using Object.assign
Alternatively, Object.assign is in the official released and will also create a shallow copy of the object.
Note the empty <> as the first argument, this will ensure you don’t mutate the original object 👍
# 3. Using JSON
This final way will give you a deep copy. Now I will mention, this is a quick and dirty way of deep cloning an object. For a more robust solution, I would recommend using something like lodash
# Lodash DeepClone vs JSON
Here’s a comment from the community. Yes, it was for my previous post, How to Deep Clone an Array
. But the idea still applies to objects.
: I’d like you to note that there are some differences between deepClone and JSON.stringify/parse.
Here’s an example:
: The JSON method has troubles with circular dependencies. Furthermore, the order of properties in the cloned object may be different.
# Shallow Clone vs Deep Clone
# Shallow Copy
Let’s clone our object using spread:
So we changed our cloned object by changing the city. Let’s see the output.
A shallow copy means the first level is copied, deeper levels are referenced.
# Deep Copy
Let’s take the same example but applying a deep copy using «JSON»
As you can see, the deep copy is a true copy for nested objects. Often time shallow copy is good enough, you don’t really need a deep copy. It’s like a nail gun vs a hammer. Most of the time the hammer is perfectly fine. Using a nail gun for some small arts and craft is often case an overkill, a hammer is just fine. It’s all about using the right tool for the right job 🤓
# Performance
# Community Input
# Object.assign vs Spread
: It’s important to note that Object.assign is a function which modifies and returns the target object. In Samantha’s example using the following,
<> is the object that is modified. The target object is not referenced by any variable at that point, but because Object.assign returns the target object, we are able to store the resulting assigned object into the cloneFood variable. We could switch our example up and use the following:
Spread on the other hand is an operator which copies properties of one object into a new object. If we wanted to replicate the above example using spread to modify our variable food.
or we could declare food with let or var which would allow us to assign a whole new object:
# Deep Clone using External Libraries
: The only way I’ve known to do this is with the Lodash library, cloneDeep method.
4 ways to clone objects in JavaScript
Since JavaScript objects are reference types, you can not just use the equal operator ( = ) to copy an object. When you create an object in JavaScript, the value is not directory assigned to the variable. Instead, the variable only holds a reference to the value.
What’s Reference Type?
Let us look at the following example to understand what reference type means:
As you can see above, I have created an object and then assigned it to a new variable by using the = operator. Both objects output the same key-value pairs. So far, so good!
Let us now add a new key to the first object to see what happens:
Shallow Clone vs. Deep Clone
A shallow clone only copies primitive types like strings, numbers, and booleans available in the object. Any nested object or array will not be recursively copied. Instead, only a reference to the object is copied to the new object. It means that both the original object and copied object continue to refer the same nested object.
If the original object references other external objects, they are also not recursively copied when creating a shallow copy of the object. Only the references to the external objects are copied.
On the other hand, a deep clone recursively copies everything: primitive data types, nested and external objects, arrays, functions, dates, and so on. The cloned object is completely independent of the original object.
Object.assign() Method
Notice the empty <> source object as the first parameter. This is necessary to make sure that the original object is not altered. This method lacks support for old browsers like IE, and only works in modern browsers.
Take a look at this guide to learn more about the Object.assign() method.
Spread Operator
Although spread operators are around since ES6 (ESMAScript 2015), the support for cloning objects was only introduced recently in ES9 (ESMAScript 2018). So you should only consider using this approach for the latest versions of modern browsers.
JSON Methods
If your object only contains primitive types, and doesn’t include nested or external objects, arrays, Date objects, functions, and so on, you can easily create a deep clone of the object by using JSON methods: JSON.stringify() and JSON.parse() :
The JSON methods only support strings, numbers, and object literals without functions and symbol properties. You’d see a weird behavior when the object contains non-compatible values:
You should only use this approach for JSON compatible objects. For objects that contain JSON incompatible values, consider using a 3rd-party library like Lodash to create a deep clone.
Lodash’s cloneDeep() Method
Lodash provides the cloneDeep() method that recursively copies everything in the original object to the new object. It works for all data types, including functions, nested objects, arrays, and symbols.
Here is an example:
To learn more about JavaScript objects, prototypes, and classes, take a look at this article.
✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.
Быстрое клонирование объектов в JavaScript
Клонирование объектов в JavaScript довольно частая операция. К сожалению, JS не предоставляет быстрых нативных методов для решения этой задачи.
К примеру, популярная Node.JS ORM Sequelize, которую мы используем на backend-е нашего проекта, значительно теряет в производительности на предвыборке большого (1000+) количества строк, только на одном клонировании. Если вместе с этим, к примеру, в бизнес-логике использовать метод clone известной библиотеки lodash — производительность падает в десятки раз.
Но, как оказалось, не всё так плохо и современные JS-движки, такие как, например, V8 JavaScript Engine, могут успешно справляться с этой задачей, если правильно использовать их архитектурные решения. Желающим узнать как клонировать 1 млн. объектов за 30 мс — добро пожаловать под кат, все остальные могут сразу посмотреть реализацию.
Сразу хочется оговориться, что на эту тему уже немного писали. Коллега с Хабра даже делал нативное расширение node-v8-clone, но оно не собирается под свежие версии ноды, сфера его применения ограничена только бэкэндом, да и скорость его ниже предлагаемого решения.
Давайте разберемся на что тратится процессорное время во время клонирования — это две основных операции выделение памяти и запись. В целом, их реализации для многих JS-движков схожи, но далее пойдет речь о V8, как основного для Node.js. Прежде всего, чтобы понять на что уходит время, нужно разобраться в том, что из себя представляют JavaScript объекты.
Представление JavaScript объектов
JS очень гибкий язык программирования и свойства его объектам могут добавляться на лету, большинство JS-движков используют хэш-таблицы для их представления — это дает необходимую гибкость, но замедляет доступ к его свойствам, т.к. требует динамического поиска хэша в словаре. Поэтому оптимизационный компилятор V8, в погоне за скоростью, может на лету переключаться между двумя видами представления объекта — словарями (hash tables) и скрытыми классами (fast, in-object properties).
V8 везде, где это возможно, старается использовать скрытые классы для быстрого доступа к свойствам объекта, в то время как хэш-таблицы используются для представления «сложных» объектов. Скрытый класс в V8 — это ничто иное, как структура в памяти, которая содержит таблицу дескрипторов свойств объекта, его размер и ссылки на конструктор и прототип. Для примера, рассмотрим классическое представление JS-объекта:
Создание скрытого класса, каждый раз когда добавляется новое свойство может быть не эффективным, но т.к. для новых экземпляров этого же объекта скрытые классы буду переиспользованны — V8 страется использовать их вместо словарей. Механизм скрытых классов помогает избежать поиска по словарю при доступе к свойствам, а также позволяет использовать различные оптимизации основанные на классах, в т.ч. inline caching.
В связи с этим, идеальным объектом для компилятора будет объект — с конструктором в котором четко определен набор его свойств, не изменяющийся в процессе выполнения. Поэтому самой важной оптимизацией для ускорения доступа к свойствам объектов при клонировании является правильное описание его конструктора. Второй важной частью является непосредственно оптимизация самого процесса чтения-записи, о чем и пойдет речь дальше.
Динамическая генерация кода
V8 компилирует JavaScript код напрямую в машинный во время первого исполнения, без промежуточного кода или интерпретатора. Доступ к свойствам объектов при этом оптимизируется inline cache-м, машинные инструкции которого V8 может изменять прямо во время выполнения.
Рассмотрим чтение свойства объекта, в течении первоначального выполнения кода, V8 определяет его текущий скрытый класс и оптимизирует будущие обращения к нему, предсказывая что в этой секции кода объекты будут с тем же скрытым классом. Если V8 удалось предсказать корректно, то значение свойства присваивает (или получается) одной операцией. Если же предсказать верно не удалось, V8 изменяет код и удаляет оптимизацию.
Для примера, возьмем JavaScript код получающий свойство x объекта Point :
V8 генерирует следующий машинный код для чтения x :
Если скрытый класс объекта не соответствует закешированному, выполнение переходит к коду V8 который обрабатывает отсутствие inline cache-а и изменяет его. Если же классы соответствуют, что происходит в большинстве случаев, значение свойства x просто получается в одну операцию.
При обработке множества объектов с одинаковым скрытым классом достигаются те же преимущества что и у большинства статических языков. Комбинация использования скрытых классов для доступа к свойствам объектов и использования кэша значительно увеличивает производительность JavaScript-кода. Именно этими оптимизациями мы и воспользуемся для ускорения процесса клонирования.
Клонирование
В принципе и всё, если бы ни одно но — писать такие конструкторы для всех видов объектов в системе достаточно накладно, так же, объекты могут иметь сложную вложенную структуру. Для того чтобы упростить работу с этими оптимизациями мною была написана библиотека создающая конструкторы клонирования для переданного объекта любой вложенности.
Принцип работы библиотеки очень прост — она получает на вход объект, генерирует по его структуре конструктор клонирования, который в дальнейшем можно использовать для клонирования объектов этого типа.
Is this a good way to clone an object in ES6?
Googling for «javascript clone object» brings some really weird results, some of them are hopelessly outdated and some are just too complex, isn’t it as easy as just:
Is there anything wrong with this?
10 Answers 10
Trending sort
Trending sort is based off of the default sorting method — by highest score — but it boosts votes that have happened recently, helping to surface more up-to-date answers.
It falls back to sorting by highest score if no posts are trending.
Switch to Trending sort
This is good for shallow cloning. The object spread is a standard part of ECMAScript 2018.
For deep cloning you’ll need a different solution.
const clone = <. original>to shallow clone
const newobj = <. original, prop: newOne>to immutably add another prop to the original and store as a new object.
EDIT: When this answer was posted, <. obj>syntax was not available in most browsers. Nowadays, you should be fine using it (unless you need to support IE 11).
However, this won’t make a deep clone. There is no native way of deep cloning as of yet.
EDIT: As @Mike ‘Pomax’ Kamermans mentioned in the comments, you can deep clone simple objects (ie. no prototypes, functions or circular references) using JSON.parse(JSON.stringify(input))
If the methods you used isn’t working well with objects involving data types like Date, try this
Deep clone object
You can do it like this as well,
if you don’t want to use json.parse(json.stringify(object)) you could create recursively key-value copies:
But the best way is to create a class that can return a clone of it self
Following on from the answer by @marcel I found some functions were still missing on the cloned object. e.g.
where on MyObject I could clone methodA but methodB was excluded. This occurred because it is missing
which meant it did not show up in
Instead I switched over to
which will include non-enumerable keys.
I also found that the prototype (proto) was not cloned. For that I ended up using
PS: Frustrating that I could not find a built in function to do this.
structured Clone you can Used this method
But Object.assign() not create a deep clone
To fix that, we should use the cloning loop that examines each value of userHow to clone js object and, if it’s an object, then replicate its structure as well. That is called a “deep cloning”.
There’s a standard algorithm for deep cloning that handles the case above and more complex cases, called the Structured cloning algorithm. In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library lodash the method is called _.cloneDeep(obj).