📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
복사에도 두 가지가 있다.
자바스크립트에서 변수에 객체나 배열을 할당할 때, 우리는 이것이 얕은 복사인지 깊은 복사인지 구별해야 한다.
물론, 아무것도 모를 때는 이게 복사면 복사지 얕고 깊은 정도가 따로 있나 싶지만 이게 무엇이냐에 따라서 원본 객체나 배열에도 영향이 가게 되고, 결국 코드 전체에도 영향을 줄 수 있는 문제이기 때문에 우리는 얕은 복사와 깊은 복사에 대해서 확실히 알고 가야 한다.
얕은 복사와 깊은 복사는 이름에서부터 얕고 깊다고 되어 있기 때문에, 우리는 대충 이게 어떤 것을 뜻하는지 알 수 있다.
얕은 복사는 말 그대로 얕다고 되어 있기 때문에 무언가를 복사하지만 확실히 복사되는 게 아닐 것 같다는 느낌이 들고, 깊은 복사는 얕은 복사와 다르게 확실하게 복사되는 느낌이겠구나라고 감을 잡을 수 있다. 그렇다면, 무엇을 기준으로 얕고 깊은 게 다른 걸까?
얕은 복사와 깊은 복사의 정의
얕은 복사
얕은 복사는 객체 또는 배열을 복사할 때, 참조된 객체나 배열의 메모리 주소만 복사하는 것을 의미한다. 이 말인즉슨, 복사본과 원본이 같은 메모리 주소를 가진다는 것으로 복사본을 수정하게 된다면 원본도 함께 수정된다는 것이다.
깊은 복사
깊은 복사는 객체 또는 배열을 복사할 때, 참조된 객체나 배열의 모든 내용을 복사하는 것을 의미한다. 복사본과 원본이 서로 다른 메모리 주소를 가지기 때문에, 얕은 복사와 달리 복사본을 수정해도 원본에 전혀 영향을 미치지 않는다.
두 가지 복사의 차이점, 즉 얕고 깊고의 정도를 나누는 차이는 바로 '메모리 주소'이다. 얕은 복사의 경우 메모리 주소만 복사했지만, 깊은 복사는 메모리 주소가 아닌 그 안의 값을 그대로 복사한다. 복사는 했지만, 완전히 다른 객체 또는 배열이라고 생각하면 된다.
얕은 복사의 방법 및 예시
일반적인 복사
const obj1 = {a: 1, b: 2, c: {d: 3}};
const obj2 = obj1;
obj2.b = 4; // 기존 값 수정
console.log(obj1.b); // 4
obj2.e = 5; // 새로운 값 추가
console.log(obj1.e); // 5
우리가 일반적으로 변수에 객체를 담아서 복사하는 방법은 얕은 복사이다. 위 코드에서 원본 객체는 obj1, 복사본 객체는 obj2이다. 복사본인 obj2에 값을 수정하거나 추가했더니, 원본 객체인 obj1에서도 그대로 값이 수정되고 추가되었다.
Object.assign()
const obj1 = {a: 1, b: 2, c: {d: 3}};
const obj2 = Object.assign({}, obj1);
obj2.b = 4; // 기존 값 수정
console.log(obj1.b); // 4
obj2.e = 5; // 새로운 값 추가
console.log(obj1.e); // undefined
얕은 복사의 대표적인 방법으로는 Object.assign()가 있는데, 이 메서드는 객체와 객체를 서로 병합할 때 사용하는 메서드이다. (같은 메모리 주소를 참조하는 객체를 만들어야 할 때 유용하게 사용되는 메서드이므로 기억해 두자.)
Object.assign()으로 복사된 객체는 얕은 복사의 정의대로 원본 객체와 같은 메모리 주소를 참조하지만, 새로운 값을 추가하는 데 있어서는 원본 객체의 메모리를 참조하지 않는다는 특징을 가지고 있다. 기존의 값만 같은 메모리를 참조하고, 그 이후 추가되는 값에 대해서는 각자의 메모리에 할당한다.
깊은 복사의 방법 및 예시
전개 연산자 사용
const obj1 = {a: 1, b: 2, c: {d: 3}};
const obj2 = {...obj1};
obj2.a = 2; // 1차원에서는 깊은 복사
console.log(obj1.a); // 1
obj2.c.d = 10; // 2차원에서는 얕은 복사
console.log(obj1.c.d) // 10
전개 연산자를 사용해서 객체를 복사하면, 제약이 있기는 하지만 일반적으로 깊은 복사로 복사가 된다. 하지만, 제약이 있다고 말한 것처럼 1차원 값에 대해서만 깊은 복사가 되고, 2차원 값부터는 모두 얕은 복사가 된다.
위의 예시에서 보면 1차원 값인 obj2.a에 대해서는 깊은 복사가 되었기 때문에 서로 다른 메모리에 할당되었다. 하지만, obj2.c.d 같은 경우에는 2차원 값이기 때문에 obj1.c.d의 값도 같이 변한 것을 확인할 수 있다.
JSON.parse(JSON.stringify())
const obj1 = {a: 1, b: 2, c: {d: 3}};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.a = 10;
console.log(obj1.a); // 1
obj2.c.d = 30;
console.log(obj1.c.d); // 3
Object.assign()처럼 제약사항이 있는 게 아닌 완전한 깊은 복사를 하고 싶다면, JSON으로 한 번 감쌌다가 풀면 된다.
이 방법은 깊은 복사의 가장 일반적인 방법으로 JSON 형식을 이용해서 값을 가져오는 것이기 때문에 같은 메모리 주소를 참조하려야 참조할 수가 없는 방법이다.
하지만, 이 방법도 객체나 배열에 함수가 포함되어 있거나 undefined, infinity, NaN과 같은 값을 가지고 있을 경우에는 제대로 작동하지 않을 수 있다. (그렇지만 보통의 객체라면 이런 값들은 포함되지 않은 경우가 많을 테니 특별한 상황이 아니라면 이 방법을 사용하자.)
lodash 모듈 사용하기
// npm install lodash
const _ = require('lodash');
const obj1 = {a: 1, b: 2, c: {d: 3}};
const obj2 = _.cloneDeep(obj1); // 깊은 복사 완료
전개 연산자나 JSON.stringify()처럼 어떠한 제약사항도 없이 무조건 깊은 복사가 되는 방법 중 한 가지는 바로 lodash 라이브러리를 사용하는 것이다. lodash의 cloneDeep()은 객체나 배열을 깊은 복사시켜 주는 메서드로, lodash 라이브러리를 이용한다면 강력하면서도 쉽게 깊은 복사를 할 수 있다.
1. 얕은 복사는 참조값이 같음, 깊은 복사는 참조값이 다름.
2. var a = [1, 2, 3];
var b = a; // 얕은 복사
var c = [...a]; // 깊은 복사
b[0] = 4; // a[0]도 4로 변함 (얕은 복사했기 때문에 참조값이 서로 같음)
c[0] = 4; // a[0]은 그대로 1 (깊은 복사했기 때문에 참조값이 서로 다름)
'JavaScript' 카테고리의 다른 글
[JavaScript] 객체와 배열을 닮은 ES6 Map, Set 객체 이해하기 (1) | 2023.04.28 |
---|---|
[JavaScript] 도메인 정보를 다룰 때 사용하면 유용한 URL 객체 (0) | 2023.03.12 |
[JavaScript] 배열의 값이 조건에 맞는지 확인하는 every(), some() (0) | 2023.02.09 |
[JavaScript] 쿠키, 세션, 로컬 스토리지 비교하기 (0) | 2023.02.02 |
[JavaScript] 이벤트 캡쳐링과 이벤트 버블링 (+ 이벤트 위임) (0) | 2023.01.19 |