[JS교양] 객체의 얕은 복사, 깊은 복사 | 얕은 지식으로 정리한 글
- 프론트/자바스크립트
- 2024. 7. 26.
나는 자바스크립트를 대~충 배워 대~충 쓰고 있었다.
아직도 배우는 단계인데 모르는 지식이 굉장히 많고 대충 사용하면 안되겠구나라는걸 느끼게 된 것 중 하나인
객체의 얕은 복사와 깊은 복사를 정리한 글. (얕은 지식으로 정리하였음)
객체
JS에서는 데이터 타입을 크게 원시 데이터 타입(Primitive Data Types)과 참조 데이터 타입(Reference Data Types) 두 가지로 나눌 수 있다. 객체는 둘 중 참조 데이터 타입 이다. 객체는 변수에 직접 값을 저장하지 않고 값이 저장된 메모리 주소(참조)를 저장한다. 객체의 속성에 접근해서 데이터를 변경하면 `데이터를 가리키는 주소값이` 변경 된다.(데이터가 변경되면 데이터의 주소값이 바뀌기 때문)
- 메모리에는 데이터가 저장되는 주소가 있다. (`데이터 주소`)
- 속성에는 `데이터 주소`를 가리키는 주소가 있다. (`데이터를 가리키는 주소` 라고 칭하겠음 정확한 명칭 모름,,)
- 객체 `A`를 객체 `B`에 복사하면 `A`의 ` 데이터를 가리키는 주소`를 복사한다.
- `B`에서 데이터를 변경하면 ` 데이터를 가리키는 주소 `가 가리키는 `데이터 주소`를 변경한다
- ` 데이터를 가리키는 주소 `를 공유하기 때문에 `B`에서 변경한 주소를 `A`에서도 참조하게 된다.
- 대충 이렇게 주소값이 변경이 되는 특징을 가진 것들을 가변성 자료라고 한다.
요약하면 객체를 대충 복사해서 사용하면 원본 객체 데이터에 영향을 끼칠 수 있으니 가변성의 리스크를 염두하고 코드를 작성해야한다.
코드로 보면 좀 쉽게 이해가 갈 것이다. 대충 복사하면 어떤일이 발생하는지 코드로 예시를 봐보도록 하자.
// user객체 생성 (name = 길동)
var user = {
name: "길동",
age: 30,
}
// newUser에 user복사
var newUser = user;
console.log(user.name); // "길동" 출력
newUser.name = "순신"; // 새 객체의 name 변경
console.log(user.name); // "순신" 출력 (원본 객체도 같이 변경됨)
복사한 객체의 속성을 변경했음에도 원본 객체까지 변경된 것을 볼 수 있다. (`자체주소` 를 공유하기 때문)
- 메모리 어딘가에는 "길동"이 담긴 `데이터 주소`가 있다.
- user의 name 속성에는 "길동"이라는 ` 데이터를 가리키는 주소 `가 있다.
- newUser 객체를 복사하면 "길동"이라는 값이 담긴 ` 데이터를 가리키는 주소 ` 를 복사한다.
- newUser의 name 속성을 "순신"으로 변경하면 "순신"이 담긴 새로운 `데이터 주소` 를 ` 데이터를 가리키는 주소 ` 에 덮어쓴다. (= ` 데이터를 가리키는 주소` 를 공유하기 때문에 원본 객체에서도 `데이터 주소`값이 바뀌는 것)
객체 올바르게 복사하기(1)
그래서 원본 객체를 유지할 수 있으면서, 같은 데이터를 지닌 객체를 복사할때는 어떻게 하는가?
새로운 객체에 값만 복사해주면 된다.
var user = {
name: "길동",
age: 30,
}
// 복사 객체 반환 함수
function copyObj(user, name){
return {
name: name,
age: user.age,
}
}
var newUser = copyObj(user, "순신");
console.log(user.name); // 길동 출력 (원본 유지)
console.log(newUser.name) // 순신 출력
원본 객체의 데이터는 복사하면서 새로운 객체를 생성하기 때문에 값은 같아도 참조 하는 주소값은 개별적인 공간에 저장된다. (= 복사본을 변경해도 원본은 유지된다)
문제점
객체의 속성이 적다면 별 문제 없겠지만 아주아주 많은 속성의 객체를 복사하려면 굉장한 노동력이 필요할 것이다.(하드코딩 문제점)
객체 올바르게 복사하기(2) - 얕은 복사
하드코딩 문제에 대한 해결 방안 코드 예시
var user = {
name: "길동",
age: 30,
}
var copyObject = function(target){
var result = {};
// for ~ in으로 전체 순회하며 복사
for(var prop in target){
result[prop] = target[prop];
}
return result;
}
var newUser = copyObject(user);
- for(~in) 문법을 사용해서 객체의 모든 요소를 순회한다.
- 순회하며 복사될 객체의 속성과 값을 저장한다.
- 복사가 완료된 객체를 반환한다.
문제점
안타깝게도 이 방법(얕은 복사)도 약간의 문제가 있는데, 중첩객체 복사시 일반 데이터는 정상적으로 복사가 가능하지만 객체안의 객체(중첩된 객체) 에 대한 복사는 제대로 수행되지 않는다. (중첩된 객체일 경우 주소값을 복사하게됨)
예시)
var info = {
name: "길동",
url: {
blog: "reactjy2.tistory.com",
git: "www.github.com",
}
}
`name `속성은 우리가 바라는대로 복사가 되겠지만 `url` 속성은 그렇지 않을 것이다.
객체를 올바르게 복사하기(3) - 깊은 복사
찐막이다. 깊은 복사의 패턴을 사용한다면 우리가 원하는대로 복사가 될 것이다. 단, 재귀함수를 사용한다. (어렵다)
var info = {
name: "길동",
url:{
blog: "www.tistory.com",
github: "www.github.com",
}
}
var copyObjectDeep = function(target){
var result = {};
if(typeof target === 'object' && target !== null){
for(var prop in target){
result[prop] = copyObjectDeep(target[prop]); // 본인 호출
}
}else{
return target;
}
return result;
}
var newUser = copyObjectDeep(info);
info객체
- `info` 객체는 `name`과 `url` 두 개의 속성을 가진다. `url` 속성은 또 다른 객체를 갖고 있다. (중첩객체)
copyObjectDeep() 함수
- `target`을 매개변수로 받는다
- `result`는 `target`을 복사해서 반환할 변수
- `target` 이 객체인지 확인함 (null 관련 설명은 생략)
- 객체일 경우 `for...in`반복문을 사용해서 순회함
- 각 속성(`prop`)마다 `copyObjectDeep(target[prop])`를 호출하여 재귀적으로 속성의 값을 복사 => 중첩된 객체의 경우에도 깊은 복사
- `target`이 객체가 아닌 경우 (`else`), 즉 원시 데이터 타입인 경우에는 값을 그대로 반환
- `result` 반환
'프론트 > 자바스크립트' 카테고리의 다른 글
[JS기록] 중복된 함수 하나로 합쳐버리기 (feat. callback) (0) | 2024.07.29 |
---|---|
[JS] DOM | 입맛대로 HTML 변경하기 (0) | 2024.07.29 |
[JS교양] Property Shorthand | 객체에 속성 추가하는게 귀찮아요 (0) | 2024.07.25 |
[JS교양] 객체 타입에 `const` 를 사용하는 것에 대하여 (0) | 2024.07.24 |
[JS교양] null == undefined = true (0) | 2024.07.24 |