You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/04-object-basics/02-object-copy/article.md
+96-35Lines changed: 96 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,7 +37,7 @@ And here's how it's actually stored in memory:
37
37
38
38
The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it.
39
39
40
-
We may think of an object variable, such as `user`, as like a sheet of paper with the address of the object on it.
40
+
We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it.
41
41
42
42
When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object.
43
43
@@ -100,15 +100,37 @@ alert( a == b ); // false
100
100
101
101
For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj ==5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake.
102
102
103
+
````smart header="Const objects can be modified"
104
+
An important side effect of storing objects as references is that an object declared as `const` *can* bemodified.
105
+
106
+
For instance:
107
+
108
+
```js run
109
+
const user = {
110
+
name: "John"
111
+
};
112
+
113
+
*!*
114
+
user.name = "Pete"; // (*)
115
+
*/!*
116
+
117
+
alert(user.name); // Pete
118
+
```
119
+
120
+
It might seem that the line `(*)` would cause an error, but it does not. The value of`user` is constant, it must always reference the same object, but properties of that object are free to change.
121
+
122
+
In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
123
+
124
+
That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter <info:property-descriptors>.
125
+
````
126
+
103
127
## Cloning and merging, Object.assign [#cloning-and-merging-object-assign]
104
128
105
129
So, copying an object variable creates one more reference to the same object.
106
130
107
-
But what if we need to duplicate an object? Create an independent copy, a clone?
108
-
109
-
That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need -- copying by reference is good most of the time.
131
+
But what if we need to duplicate an object?
110
132
111
-
But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
133
+
We can create a newobject and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level.
112
134
113
135
Like this:
114
136
@@ -133,21 +155,22 @@ clone.name = "Pete"; // changed the data in it
133
155
alert( user.name ); // still John in the original object
134
156
```
135
157
136
-
Also we can use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) for that.
158
+
We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign).
137
159
138
160
The syntax is:
139
161
140
162
```js
141
-
Object.assign(dest, [src1, src2, src3...])
163
+
Object.assign(dest, ...sources)
142
164
```
143
165
144
166
- The first argument `dest` is a target object.
145
-
- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects.
146
-
- It copies the properties of all source objects `src1, ..., srcN` into the target `dest`. In other words, properties of all arguments starting from the second are copied into the first object.
147
-
- The call returns `dest`.
167
+
- Further arguments is a list of source objects.
148
168
149
-
For instance, we can use it to merge several objects into one:
150
-
```js
169
+
It copies the properties of all source objects into the target `dest`, and then returns it as the result.
170
+
171
+
For example, we have `user` object, let's add a couple of permissions to it:
We also can use `Object.assign` to replace `for..in` loop for simple cloning:
200
+
We also can use `Object.assign` to perform a simple object cloning:
175
201
176
-
```js
202
+
```js run
177
203
let user = {
178
204
name: "John",
179
205
age: 30
@@ -182,15 +208,18 @@ let user = {
182
208
*!*
183
209
let clone = Object.assign({}, user);
184
210
*/!*
211
+
212
+
alert(clone.name); // John
213
+
alert(clone.age); // 30
185
214
```
186
215
187
-
It copies all properties of `user` into the empty object and returns it.
216
+
Here it copies all properties of `user` into the empty object and returns it.
188
217
189
218
There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial.
190
219
191
220
## Nested cloning
192
221
193
-
Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them?
222
+
Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects.
194
223
195
224
Like this:
196
225
```js run
@@ -205,9 +234,7 @@ let user = {
205
234
alert( user.sizes.height ); // 182
206
235
```
207
236
208
-
Now it's not enough to copy `clone.sizes=user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes:
209
-
210
-
Like this:
237
+
Now it's not enough to copy `clone.sizes = user.sizes`, because `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes:
211
238
212
239
```js run
213
240
let user = {
@@ -223,42 +250,76 @@ let clone = Object.assign({}, user);
223
250
alert( user.sizes === clone.sizes ); // true, same object
224
251
225
252
// user and clone share sizes
226
-
user.sizes.width++; // change a property from one place
227
-
alert(clone.sizes.width); //51, see the result from the other one
253
+
user.sizes.width = 60; // change a property from one place
254
+
alert(clone.sizes.width); // 60, get the result from the other one
228
255
```
229
256
230
-
To fix that, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning".
257
+
To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of`user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning.
231
258
232
-
We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
233
259
234
-
````smart header="Const objects can be modified"
235
-
An important side effect of storing objects as references is that an object declared as `const` *can* bemodified.
260
+
### structuredClone
236
261
237
-
For instance:
262
+
The call `structuredClone(object)` clones the `object`with all nested properties.
263
+
264
+
Here's how we can use it in our example:
238
265
239
266
```js run
240
-
const user = {
241
-
name: "John"
267
+
let user = {
268
+
name: "John",
269
+
sizes: {
270
+
height: 182,
271
+
width: 50
272
+
}
242
273
};
243
274
244
275
*!*
245
-
user.name = "Pete"; // (*)
276
+
let clone = structuredClone(user);
246
277
*/!*
247
278
248
-
alert(user.name); // Pete
279
+
alert( user.sizes === clone.sizes ); // false, different objects
280
+
281
+
// user and clone are totally unrelated now
282
+
user.sizes.width = 60; // change a property from one place
283
+
alert(clone.sizes.width); // 50, not related
249
284
```
250
285
251
-
It might seem that the line `(*)` would cause an error, but it does not. The value of`user` is constant, it must always reference the same object, but properties of that object are free to change.
286
+
The `structuredClone` method can clone most data types, such as objects, arrays, primitive values.
252
287
253
-
In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
288
+
It also supports circular references, when an object property references the object itself (directly or via a chain or references).
254
289
255
-
That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter <info:property-descriptors>.
256
-
````
290
+
For instance:
291
+
292
+
```js run
293
+
let user = {};
294
+
// let's create a circular reference:
295
+
// user.me references the user itself
296
+
user.me= user;
297
+
298
+
let clone =structuredClone(user);
299
+
alert(clone.me=== clone); // true
300
+
```
301
+
302
+
As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well.
303
+
304
+
Although, there are cases when `structuredClone` fails.
305
+
306
+
For instance, when an object has a function property:
307
+
308
+
```js run
309
+
// error
310
+
structuredClone({
311
+
f:function() {}
312
+
});
313
+
```
314
+
315
+
Function properties aren't supported.
316
+
317
+
To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
257
318
258
319
## Summary
259
320
260
321
Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself.
261
322
262
323
All operations via copied references (like adding/removing properties) are performed on the same single object.
263
324
264
-
To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
325
+
To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function`structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
0 commit comments