Skip to content

Commit 10ebca8

Browse files
authored
Merge pull request #129 from odsantos/fix-object-basics
Fix object basics
2 parents b3ece11 + 26198e9 commit 10ebca8

21 files changed

+407
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Object copying, references
2+
3+
One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference".
4+
5+
Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value".
6+
7+
For instance:
8+
9+
```js
10+
let message = "Hello!";
11+
let phrase = message;
12+
```
13+
14+
As a result we have two independent variables, each one is storing the string `"Hello!"`.
15+
16+
![](variable-copy-value.svg)
17+
18+
Objects are not like that.
19+
20+
**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.**
21+
22+
Here's the picture for the object:
23+
24+
```js
25+
let user = {
26+
name: "John"
27+
};
28+
```
29+
30+
![](variable-contains-reference.svg)
31+
32+
Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it.
33+
34+
**When an object variable is copied -- the reference is copied, the object is not duplicated.**
35+
36+
For instance:
37+
38+
```js no-beautify
39+
let user = { name: "John" };
40+
41+
let admin = user; // copy the reference
42+
```
43+
44+
Now we have two variables, each one with the reference to the same object:
45+
46+
![](variable-copy-reference.svg)
47+
48+
We can use any variable to access the object and modify its contents:
49+
50+
```js run
51+
let user = { name: 'John' };
52+
53+
let admin = user;
54+
55+
*!*
56+
admin.name = 'Pete'; // changed by the "admin" reference
57+
*/!*
58+
59+
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
60+
```
61+
62+
The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use another key (`user`) we can see changes.
63+
64+
## Comparison by reference
65+
66+
The equality `==` and strict equality `===` operators for objects work exactly the same.
67+
68+
**Two objects are equal only if they are the same object.**
69+
70+
Here two variables reference the same object, thus they are equal:
71+
72+
```js run
73+
let a = {};
74+
let b = a; // copy the reference
75+
76+
alert( a == b ); // true, both variables reference the same object
77+
alert( a === b ); // true
78+
```
79+
80+
And here two independent objects are not equal, even though both are empty:
81+
82+
```js run
83+
let a = {};
84+
let b = {}; // two independent objects
85+
86+
alert( a == b ); // false
87+
```
88+
89+
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 occur very rarely, usually as a result of a coding mistake.
90+
91+
## Cloning and merging, Object.assign
92+
93+
So, copying an object variable creates one more reference to the same object.
94+
95+
But what if we need to duplicate an object? Create an independent copy, a clone?
96+
97+
That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time.
98+
99+
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.
100+
101+
Like this:
102+
103+
```js run
104+
let user = {
105+
name: "John",
106+
age: 30
107+
};
108+
109+
*!*
110+
let clone = {}; // the new empty object
111+
112+
// let's copy all user properties into it
113+
for (let key in user) {
114+
clone[key] = user[key];
115+
}
116+
*/!*
117+
118+
// now clone is a fully independent object with the same content
119+
clone.name = "Pete"; // changed the data in it
120+
121+
alert( user.name ); // still John in the original object
122+
```
123+
124+
Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
125+
126+
The syntax is:
127+
128+
```js
129+
Object.assign(dest, [src1, src2, src3...])
130+
```
131+
132+
- The first argument `dest` is a target object.
133+
- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects.
134+
- 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.
135+
- The call returns `dest`.
136+
137+
For instance, we can use it to merge several objects into one:
138+
```js
139+
let user = { name: "John" };
140+
141+
let permissions1 = { canView: true };
142+
let permissions2 = { canEdit: true };
143+
144+
*!*
145+
// copies all properties from permissions1 and permissions2 into user
146+
Object.assign(user, permissions1, permissions2);
147+
*/!*
148+
149+
// now user = { name: "John", canView: true, canEdit: true }
150+
```
151+
152+
If the copied property name already exists, it gets overwritten:
153+
154+
```js run
155+
let user = { name: "John" };
156+
157+
Object.assign(user, { name: "Pete" });
158+
159+
alert(user.name); // now user = { name: "Pete" }
160+
```
161+
162+
We also can use `Object.assign` to replace `for..in` loop for simple cloning:
163+
164+
```js
165+
let user = {
166+
name: "John",
167+
age: 30
168+
};
169+
170+
*!*
171+
let clone = Object.assign({}, user);
172+
*/!*
173+
```
174+
175+
It copies all properties of `user` into the empty object and returns it.
176+
177+
## Nested cloning
178+
179+
Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them?
180+
181+
Like this:
182+
```js run
183+
let user = {
184+
name: "John",
185+
sizes: {
186+
height: 182,
187+
width: 50
188+
}
189+
};
190+
191+
alert( user.sizes.height ); // 182
192+
```
193+
194+
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:
195+
196+
Like this:
197+
198+
```js run
199+
let user = {
200+
name: "John",
201+
sizes: {
202+
height: 182,
203+
width: 50
204+
}
205+
};
206+
207+
let clone = Object.assign({}, user);
208+
209+
alert( user.sizes === clone.sizes ); // true, same object
210+
211+
// user and clone share sizes
212+
user.sizes.width++; // change a property from one place
213+
alert(clone.sizes.width); // 51, see the result from the other one
214+
```
215+
216+
To fix that, we should use the 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".
217+
218+
There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data).
219+
220+
We can use recursion to implement it. Or, not to reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
221+
222+
## Summary
223+
224+
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.
225+
226+
All operations via copied references (like adding/removing properties) are performed on the same single object.
227+
228+
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).
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
File renamed without changes.

0 commit comments

Comments
 (0)