본문 바로가기
코드잇 스프린트 4기/위클리 페이퍼

[JavaScript] 얕은 복사, 깊은 복사

by devwqc 2024. 1. 20.

코드잇 스프린트 4기
4주 차 위클리 페이퍼

데이터 타입

자바스크립트에서 데이터 타입은 크게 기본형 타입, 참조형 타입이 있다.

기본형 타입은 값의 주소를 참조한다.

참조형 타입은 값의 주솟값들로 이루어진 묶음을 가리키는 주소를 참조한다.

기본형 타입의 예를 보면

1 let a = 'codeit';
2 let b = a;
3 a = 'codeit sprint';
4 console.log(a); // 'codeit sprint'
5 console.log(b); // 'codeit'

1 라인'codeit'이라는 데이터를 메모리 공간에 확보하고 그 메모리 공간의 주소를 식별자 a가 저장된 메모리 공간에 저장한다.
2 라인은 식별자 a가 저장하고 있는 'codeit'의 메모리 주소를 복사한다.
결국 둘은 같은 데이터가 저장된 메모리 공간을 바라보게 된다.
3 라인a'codeit sprint'를 재할당 하게 되는데 이때 메모리 공간에 저장된 'codeit''codeit sprint'로 변경하는 것이 아닌 새로운 메모리 공간을 확보해 'codeit sprint'를 저장하고 그 주소를 식별자 a에 저장하게 된다.
결국 a'codeit sprint'가 저장된 주소를 저장하고 b'codeit'이 저장된 주소를 저장하게 되어 console.log를 출력하면 서로 다른 결과가 나온다.
위와 같이 기본형 타입은 값을 재할당할 때 메모리 공간을 새로 확보해 저장하고 이러한 현상을 불변(immutable)하다고 한다.

참조형 타입의 예를 보면

1 let a = {
2   name: 'codeit',
3   age: '29',
4 }
5 let b = a;
6 a.name = 'codeit sprint';
7 console.log(a.name); // 'codeit sprint'
8 console.log(b.name); // 'codeit sprint'

1 ~ 4 라인은 데이터를 저장할 메모리 공간을 확보하고 그 메모리 공간의 주소를 식별자 a가 저장된 메모리 공간에 저장한다. 그런데 데이터가 데이터의 집합(객체)로 이루어져 별도로 객체의 프로퍼티 식별자(name, age)들이 저장될 공간을 마련하고 그 주소들을 저장한다. 그리고 각 식별자들의 값('codeit', 29)을 메모리 공간에 저장하고 그 주소를 각 프로퍼티 식별자가 저장된 메모리에 저장한다.
5 라인a가 저장하고 있는 주소를 복사한다. 그리고 그 주소는 프로퍼티 식별자(name, age)가 저장된 주소들의 묶음이다.
6 라인a.name'codeit sprint'를 재할당 하게 되는데 메모리 공간을 새로 확보해 저장 후 그 주소를 a.name에 저장한다. 즉, a가 저장하고 있는 주소는 변경사항이 없이 a의 값을 변경할 수 있는데 이러한 현상을 가변(mutable)하다고 한다.
결국 ab는 여전히 같은 주소를 저장하고 있고 console.log를 출력하면 서로 같은 결과가 나오게 된다.

복사

참조형 타입을 기본형 타입을 복사할 때처럼 복사하면 주소값만 복사해서 프로퍼티가 변화하면 서로 영향이 끼친다는 건 알게 되었다.
참조형 타입의 가변은 어디까지나 내부 프로퍼티를 변경할 때 이루어지고 데이터 자체를 변경하게 되면 불변을 띈다.

데이터를 변경(새로운 객체 생성)의 예를 보면

1 function copyObj(oldObj) {
2   const newObj = {};
3   for (const key in oldObj) {
4     newObj[key] = oldObj[key]; 
5   }
6   return newObj;
7 };
8 let a = {
9   name: 'codeit',
10  age: '29',
11  team: {
12    color: 'pupple',
13    code: 'fighting'
14  }
15 };
16 let b = copyObj(a);
17 a.name = 'codeit sprint';
18 console.log(a.name); // 'codeit sprint'
19 console.log(b.name); // 'codeit'
20 a.team.code = 'happy';
21 console.log(a.team.code); // 'happy'
22 console.log(b.team.code); // 'happy'

16 라인에서 bcopyObj함수를 통해 새로운 객체를 만들어주어 a와 독립적인 객체를 만들어주었다.
17 ~ 19 라인을 보면 a.name의 값을 변경해도 b.name에는 영향이 끼치지 않은 것이 보인다.
하지만 이번 예제에선 객체 a의 프로퍼티에 객체 team이 들어있는 중첩 객체 형태이다.
team역시 참조형 타입이기 때문에 copyObj함수를 통해서는 team의 주소가 복사되었다.
20 ~ 22 라인을 보면 a.team.code의 값을 변경했을 때 b.team.code의 값이 변경됨을 확인할 수 있다.

위와 같이 바로 아래 단계의 값만 복사하는 방식을 얕은 복사(shallow copy)라고 한다.

얕은 복사의 copyObj 함수를 조금 변형하여 중첩 객체의 형태에도 제대로 된 복사가 되는 예를 보면

1 function copyObj(oldObj) {
2   let newObj = {};
3    if (typeof oldObj === 'object' && oldObj !== null) {
4     for (const key in oldObj) {
5       newObj[key] = copyObj(oldObj[key]); 
6     }
7    } else {
8        newObj = oldObj;
9    }
10   return newObj;
11 };
12 let a = {
13   name: 'codeit',
14   age: '29',
15   team: {
16     color: 'pupple',
17     code: 'fighting'
18   }
19 };
20 let b = copyObj(a);
21 a.team.code = 'happy';
22 console.log(a.team.code); // 'happy'
23 console.log(b.team.code); // 'fighting'

3 라인에서 typeof null의 값은 object이기 때문에 별도로 oldObj !== null를 처리하였다.
내부 프로퍼티가 참조형 타입일 경우 재귀를 통해 그 내부까지 완전 복사를 진행하였다.

이렇게 중첩 객체를 복사할 때 완전 독립된 새로운 복사본을 만드는 것을 깊은 복사(deep copy)라고 한다.


감사합니다.

'코드잇 스프린트 4기 > 위클리 페이퍼' 카테고리의 다른 글

[JavaScript] 이벤트 버블링, 캡처링, 위임  (0) 2024.01.27
[JavaScript] var, let, const  (0) 2024.01.20
[Git] Git-flow  (0) 2024.01.12
[Git] 머지(merge)  (0) 2024.01.12
[CSS] 포지션(Position)  (0) 2024.01.07

댓글