|
4 | 4 |
|
5 | 5 | 원시값(문자열, 숫자, 불린 값)은 '값 그대로' 저장·할당되고 복사되는 반면에 말이죠. |
6 | 6 |
|
7 | | -예시: |
| 7 | +값을 복사할 때 내부에서 어떤 일이 일어나는지 살펴보면 이해하기 쉽습니다. |
| 8 | + |
| 9 | +먼저 문자열 같은 원시값부터 살펴봅시다. |
| 10 | + |
| 11 | +아래에선 `message`의 복사본을 `phrase`에 넣습니다. |
8 | 12 |
|
9 | 13 | ```js |
10 | 14 | let message = "Hello!"; |
@@ -92,7 +96,31 @@ alert( a == b ); // false |
92 | 96 |
|
93 | 97 | `obj1 > obj2` 같은 대소 비교나 `obj == 5` 같은 원시값과의 비교에선 객체가 원시형으로 변환됩니다. 객체가 어떻게 원시형으로 변하는지에 대해선 곧 학습할 예정인데, 이러한 비교(객체끼리의 대소 비교나 원시값과 객체를 비교하는 것)가 필요한 경우는 매우 드물긴 합니다. 대개 코딩 실수 때문에 이런 비교가 발생합니다. |
94 | 98 |
|
95 | | -## 객체 복사, 병합과 Object.assign |
| 99 | +````smart header="상수 객체는 수정될 수 있습니다." |
| 100 | +객체가 참조로 저장된다는 사실 때문에 생기는 중요한 부수 효과가 있습니다. `const`로 선언된 객체도 *수정할 수 있습니다*. |
| 101 | + |
| 102 | +예시: |
| 103 | + |
| 104 | +```js run |
| 105 | +const user = { |
| 106 | + name: "John" |
| 107 | +}; |
| 108 | +
|
| 109 | +*!* |
| 110 | +user.name = "Pete"; // (*) |
| 111 | +*/!* |
| 112 | +
|
| 113 | +alert(user.name); // Pete |
| 114 | +``` |
| 115 | + |
| 116 | +`(*)`로 표시한 줄에서 오류가 발생할 것처럼 보일 수 있지만 그렇지 않습니다. `user`의 값은 상수이므로 항상 같은 객체를 참조해야 하지만, 그 객체의 프로퍼티는 자유롭게 변경할 수 있습니다. |
| 117 | + |
| 118 | +다시 말해, `const user`는 `user=...`처럼 `user` 전체에 새 값을 대입하려고 할 때만 오류를 일으킵니다. |
| 119 | + |
| 120 | +그렇지만 객체 프로퍼티까지 상수로 만들어야 한다면 완전히 다른 방법을 써야 합니다. 이 방법은 <info:property-descriptors> 챕터에서 다룹니다. |
| 121 | +```` |
| 122 | + |
| 123 | +## 객체 복사, 병합과 Object.assign [#cloning-and-merging-object-assign] |
96 | 124 |
|
97 | 125 | 객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어진다는 걸 배웠습니다. |
98 | 126 |
|
@@ -150,7 +178,7 @@ let permissions2 = { canEdit: true }; |
150 | 178 | Object.assign(user, permissions1, permissions2); |
151 | 179 | */!* |
152 | 180 |
|
153 | | -// now user = { name: "John", canView: true, canEdit: true } |
| 181 | +// 이제 user = { name: "John", canView: true, canEdit: true } |
154 | 182 | alert(user.name); // John |
155 | 183 | alert(user.canView); // true |
156 | 184 | alert(user.canEdit); // true |
@@ -182,7 +210,9 @@ alert(clone.name); // John |
182 | 210 | alert(clone.age); // 30 |
183 | 211 | ``` |
184 | 212 |
|
185 | | -예시를 실행하면 `user`에 있는 모든 프로퍼티가 빈 배열에 복사되고 변수에 할당됩니다. |
| 213 | +예시를 실행하면 `user`에 있는 모든 프로퍼티가 빈 객체에 복사되고, 그 객체가 반환됩니다. |
| 214 | + |
| 215 | +객체를 복제하는 다른 방법도 있습니다. 예를 들어 튜토리얼 뒷부분에서 다룰 [스프레드 문법](info:rest-parameters-spread)을 사용하면 `clone = {...user}`처럼 객체를 복제할 수 있습니다. |
186 | 216 |
|
187 | 217 | ## 중첩 객체 복사 |
188 | 218 |
|
@@ -218,21 +248,76 @@ let clone = Object.assign({}, user); |
218 | 248 |
|
219 | 249 | alert( user.sizes === clone.sizes ); // true, 같은 객체입니다. |
220 | 250 |
|
221 | | -// user와 clone는 sizes를 공유합니다. |
222 | | -user.sizes.width++; // 한 객체에서 프로퍼티를 변경합니다. |
223 | | -alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인할 수 있습니다. |
| 251 | +// user와 clone은 sizes를 공유합니다. |
| 252 | +user.sizes.width = 60; // 한 객체에서 프로퍼티를 변경합니다. |
| 253 | +alert(clone.sizes.width); // 60, 다른 객체에서 변경 사항을 확인할 수 있습니다. |
224 | 254 | ``` |
225 | 255 |
|
226 | | -이 문제를 해결하려면 `user[key]`의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 합니다. 이런 방식을 '깊은 복사(deep cloning)'라고 합니다. |
| 256 | +이 문제를 해결해 `user`와 `clone`을 진짜로 독립된 객체로 만들려면 `user[key]`의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복제하는 반복문을 사용해야 합니다. 이런 방식을 '깊은 복사(deep cloning)' 또는 '구조화 복사(structured cloning)'라고 합니다. 깊은 복사를 구현한 [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) 메서드가 있습니다. |
| 257 | + |
| 258 | +### structuredClone |
| 259 | + |
| 260 | +`structuredClone(object)`을 호출하면 중첩 프로퍼티까지 모두 포함해 `object`가 복제됩니다. |
| 261 | + |
| 262 | +앞선 예시에 이 메서드를 적용해 봅시다. |
| 263 | + |
| 264 | +```js run |
| 265 | +let user = { |
| 266 | + name: "John", |
| 267 | + sizes: { |
| 268 | + height: 182, |
| 269 | + width: 50 |
| 270 | + } |
| 271 | +}; |
| 272 | +
|
| 273 | +*!* |
| 274 | +let clone = structuredClone(user); |
| 275 | +*/!* |
| 276 | +
|
| 277 | +alert( user.sizes === clone.sizes ); // false, 서로 다른 객체입니다. |
| 278 | +
|
| 279 | +// 이제 user와 clone은 완전히 독립적입니다. |
| 280 | +user.sizes.width = 60; // 한 객체에서 프로퍼티를 변경합니다. |
| 281 | +alert(clone.sizes.width); // 50, 다른 객체에는 영향을 주지 않습니다. |
| 282 | +``` |
| 283 | + |
| 284 | +`structuredClone` 메서드는 객체, 배열, 원시값 등 대부분의 자료형을 복제할 수 있습니다. |
| 285 | + |
| 286 | +객체 프로퍼티가 객체 자신을 직접 또는 여러 참조를 거쳐 참조하는 순환 참조도 지원합니다. |
| 287 | + |
| 288 | +예시: |
| 289 | + |
| 290 | +```js run |
| 291 | +let user = {}; |
| 292 | +// 순환 참조를 만들어 봅시다. |
| 293 | +// user.me는 user 자신을 참조합니다. |
| 294 | +user.me = user; |
| 295 | +
|
| 296 | +let clone = structuredClone(user); |
| 297 | +alert(clone.me === clone); // true |
| 298 | +``` |
| 299 | + |
| 300 | +보시다시피 `clone.me`는 `user`가 아니라 `clone`을 참조합니다. 순환 참조도 올바르게 복제된 것입니다. |
| 301 | + |
| 302 | +다만 `structuredClone`이 실패하는 경우도 있습니다. |
| 303 | + |
| 304 | +예를 들어 객체에 함수 프로퍼티가 있으면 실패합니다. |
| 305 | + |
| 306 | +```js run |
| 307 | +// 에러가 발생합니다. |
| 308 | +structuredClone({ |
| 309 | + f: function() {} |
| 310 | +}); |
| 311 | +``` |
227 | 312 |
|
228 | | -깊은 복사 시 사용되는 표준 알고리즘인 [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data)을 사용하면 위 사례를 비롯한 다양한 상황에서 객체를 복제할 수 있습니다. |
| 313 | +함수 프로퍼티는 지원되지 않습니다. |
229 | 314 |
|
230 | | -자바스크립트 라이브러리 [lodash](https://lodash.com)의 메서드인 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)을 사용하면 이 알고리즘을 직접 구현하지 않고도 깊은 복사를 처리할 수 있으므로 참고하시기 바랍니다. |
| 315 | +이처럼 복잡한 경우를 처리하려면 여러 복제 방법을 조합하거나 직접 코드를 작성해야 할 수 있습니다. 바퀴를 다시 발명하지 않으려면 자바스크립트 라이브러리 [lodash](https://lodash.com)의 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) 같은 기존 구현을 사용할 수도 있습니다. |
231 | 316 |
|
232 | 317 | ## 요약 |
233 | 318 |
|
234 | 319 | 객체는 참조에 의해 할당되고 복사됩니다. 변수엔 '객체' 자체가 아닌 메모리상의 주소인 '참조'가 저장됩니다. 따라서 객체가 할당된 변수를 복사하거나 함수의 인자로 넘길 땐 객체가 아닌 객체의 참조가 복사됩니다. |
235 | 320 |
|
236 | 321 | 그리고 복사된 참조를 이용한 모든 작업(프로퍼티 추가·삭제 등)은 동일한 객체를 대상으로 이뤄집니다. |
237 | 322 |
|
238 | | -객체의 '진짜 복사본'을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 `Object.assign`이나 '깊은 복사'를 가능하게 해주는 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)를 사용하면 됩니다. 이때 얕은 복사본은 중첩 객체를 처리하지 못한다는 점을 기억해 두시기 바랍니다. |
| 323 | +객체의 '진짜 복사본'(클론)을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 `Object.assign`을 사용하거나(중첩 객체는 참조로 복사됩니다), '깊은 복사' 함수인 `structuredClone` 혹은 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) 같은 기존 구현을 사용할 수 있습니다. |
0 commit comments