참조형 데이터를 복사하는 경우의 문제점
let obj1 = {c: 10, d: 'ddd'};
let obj2 = obj1;
JavaScript
복사
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … |
데이터 | 이름: obj1
값: @3001 | 이름: obj2
값: @3001 | ||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | … |
데이터 | @5001~5002 | 10 | ‘ddd’ | |||
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … |
데이터 | 이름: c
값: @3002 | 이름: d
값: @3003 |
obj2.c = 20;
JavaScript
복사
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … |
데이터 | 이름: obj1
값: @3001 | 이름: obj2
값: @3001 | ||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | … |
데이터 | @5001~5002 | 10 | ‘ddd’ | 20 | ||
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … |
데이터 | 이름: c
값: @3004 | 이름: d
값: @3003 |
console.log(obj1.c, obj2.c);
JavaScript
복사
→ 서로의 프로퍼티 값이 다르게 나올 것 같았지만 실제론 같은 20이 출력
→ 원본이나 사본의 프로퍼티 값을 변경하면 둘 다 영향을 받음.
얕은 복사
바로 아래 단계의 값만 복사하는 방법
기존 정보를 복사해서 새로운 객체를 반환하는 함수(얕은 복사)
중첩된 객체에 대한 얕은 복사
얕은 복사 메모리 영역에서 확인해보기
•
let user2 = copyObject(user); // user2에 user에 대한 얕은 복사 실행됨
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … | ||
데이터 | 이름: user
값: @3001 | 이름: user2
값: @3006 | ||||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | 3005 | 3006 | 3007 |
데이터 | @5001~5002 | ‘lion’ | ‘lv 1’ | 'lv 2’ | ‘lv 3’ | @5003~5004 | ‘jisu’ | |
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … | ||
데이터 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | ||||
skills 객체의 변수 영역 | 주소 | 7001 | 7002 | 7003 | 7004 | … | ||
데이터 | 이름: ‘HTML’
값: @3003 | 이름: ‘CSS’
값: @3004 | 이름: ‘JS’
값: @3005 |
•
user2.name = 'jisu'; 실행
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … | ||
데이터 | 이름: user
값: @3001 | 이름: user2
값: @3006 | ||||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | 3005 | 3006 | 3007 |
데이터 | @5001~5002 | ‘lion’ | ‘lv 1’ | 'lv 2’ | ‘lv 3’ | @5003~5004 | ‘jisu’ | |
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … | ||
데이터 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | 이름: name
값: @3007 | 이름: skills
값: @7001~7003 | ||||
skills 객체의 변수 영역 | 주소 | 7001 | 7002 | 7003 | 7004 | … | ||
데이터 | 이름: ‘HTML’
값: @3003 | 이름: ‘CSS’
값: @3004 | 이름: ‘JS’
값: @3005 |
→ name은 user 객체에 직접 속한 프로퍼티로, user2가 값을 수정해도 새로운 데이터를 만들기 때문에 서로 다른 값임.
•
user.skills.HTML = 'lv 5'; // 중첩된 객체의 프로퍼티 값을 수정함
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … | |||
데이터 | 이름: user
값: @3001 | 이름: user2
값: @3006 | |||||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | 3005 | 3006 | 3007 | 3008 |
데이터 | @5001~5002 | ‘lion’ | ‘lv 1’ | 'lv 2’ | ‘lv 3’ | @5003~5004 | ‘jisu’ | ‘lv 5’ | |
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … | |||
데이터 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | 이름: name
값: @3007 | 이름: skills
값: @7001~7003 | |||||
skills 객체의 변수 영역 | 주소 | 7001 | 7002 | 7003 | 7004 | … | |||
데이터 | 이름: ‘HTML’
값: @3008 | 이름: ‘CSS’
값: @3004 | 이름: ‘JS’
값: @3005 |
⇒ 둘이 같은 값을 출력하고 있음
•
user2.skills.CSS = ‘lv 3’
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … | |||
데이터 | 이름: user
값: @3001 | 이름: user2
값: @3006 | |||||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | 3005 | 3006 | 3007 | 3008 |
데이터 | @5001~5002 | ‘lion’ | ‘lv 1’ | 'lv 2’ | ‘lv 3’ | @5003~5004 | ‘jisu’ | ‘lv 5’ | |
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … | |||
데이터 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | 이름: name
값: @3007 | 이름: skills
값: @7001~7003 | |||||
skills 객체의 변수 영역 | 주소 | 7001 | 7002 | 7003 | 7004 | … | |||
데이터 | 이름: ‘HTML’
값: @3008 | 이름: ‘CSS’
값: @3005 | 이름: ‘JS’
값: @3005 |
⇒ 이런 현상을 발생하지 않기위해 중첩된 객체에 대해서 불변 객체로 만들 필요가 있음
깊은 복사
내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법
중첩된 객체에 대한 깊은 복사
실행 결과
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | … | |||
데이터 | 이름: user
값: @3001 | 이름: user2
값: @3006 | |||||||
데이터 영역 | 주소 | 3001 | 3002 | 3003 | 3004 | 3005 | 3006 | 3007 | 3008 |
데이터 | @5001~5002 | ‘lion’ | ‘lv 1’ | 'lv 2’ | ‘lv 3’ | @5003~5004 | ‘jisu’ | ‘lv 5’ | |
프로퍼티 변수 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | … | |||
데이터 | 이름: name
값: @3002 | 이름: skills
값: @7001~7003 | 이름: name
값: @3007 | 이름: skills
값: @7004~7006 | |||||
skills 객체의 변수 영역 | 주소 | 7001 | 7002 | 7003 | 7004 | 7005 | 7006 | … | |
데이터 | 이름: ‘HTML’
값: @3008 | 이름: ‘CSS’
값: @3004 | 이름: ‘JS’
값: @3005 | 이름: ‘HTML’
값: @3003 | 이름: ‘CSS’
값: @3005 | 이름: ‘JS’
값: @3005 |
결론
어떤 객체를 복사하는 경우, 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야함. 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사가 됨
깊은 복사를 수행하는 함수
콘솔창에서 실행한 결과
깊은 복사를 처리할 수 있는 다른 방법들
•
hasOwnProperty 메서드를 활용해 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게끔 할 수 있음
•
객체를 JSON문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 것
→ 단순함에도 잘 동작함
→ But, 메서드(함수)나 숨겨진 프로퍼티인 __proto__나 getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시. (undefined, Infinity, -infinity, NaN 등은 JSON.stringify()를 통해 전달 불가능)
→객체 안에 순환 참조가 있을 경우 무한 루프에 빠지므로 주의가 필요(깊은 복사 수행 시 상황에 맞는 방법을 선택해야함)
→ httpRequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋음
JSON을 활용한 간단한 깊은 복사
정리하기
얕은 복사는 참조형 타입의 값의 바로 아래 단계의 값만 복사하는 방법
얕은 복사를 사용하여 객체를 복사하면 객체의 속성은 새로운 객체로 복사되지만, 속성값이 객체인 경우 참조 값이 복사되기 때문에 객체 내부의 값을 변경하면 원본 객체와 복사본 모두 변경
깊은 복사는 기존 값의 모든 참조가 끊어지는 것. 특히 복사할 때, 참조형 타입 값(객체)에서 내부에 있는 모든 값이 새로운 값이 되는 것
이 경우, 속성값이 객체인 경우에도 객체 내부의 값이 변경되어도 원본 객체에 영향을 미치지 않음