|
| 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 | + |
| 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 | + |
| 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 | + |
| 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). |
0 commit comments