객체와 원시 타입의 근본적인 차이 중 하나는
원시값(primitive type)은 '값 그대로' 저장-할당되고 복사되는 반면에
객체는 '참조에 의해 (by reference)'저장되고 복사된다는 것.
변수엔 객체가 그대로 저장되는 것이 아니라,
객체가 저장되어 있는 '메모리 주소'인 객체에 대한 '참조 값'이 저장된다.
객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 '참조'할 수 있는 값이 저장됩니다.
따라서 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.
let user = { name: "John" };
let admin = user; // 참조값을 복사함
변수는 두개이지만 각 변수엔 동일 객체에 대한 참조 값이 저장된다.
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨
alert(user.name); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함
참조에 의한 비교
객체 비교시 동등 연산자와 일치 연산자(==, ===)는 동일하게 적용
비교 시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환
let a = {};
let b = a; // 참조에 의한 복사
alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true
다른 예시를 보면, 두 객체가 모두 비어있는 것은 같지만 독립된 객체이기에 거짓이 반환된다.
let a = {};
let b = {}; // 독립된 두 객체
alert( a == b ); // false
객체 복사, 병합과 Object.assign
객체를 복제하고 싶다면 어떻게 해야할까? 기존에 있는 객체와
똑같으면서 독립적인 객체를 만들고 싶다면?
방법은 있지만 내장 메서드를 지원하지 않기에 조금 어렵다. 거의 복제할 일은 없지만
정말 필요하다면 새로운 객체를 만들고 기존 객체를 순회하며나서 원시 수준까지 프로퍼티를 복사하면 된다.
let user = {
name: "John",
age: 30
};
let clone = {}; // 새로운 빈 객체
// 빈 객체에 user 프로퍼티 전부를 복사해 넣습니다.
for (let key in user) {
clone[key] = user[key];
}
// 이제 clone은 완전히 독립적인 복제본이 되었습니다.
clone.name = "Pete"; // clone의 데이터를 변경합니다.
alert( user.name ); // 기존 객체에는 여전히 John이 있습니다.
Object.assign 을 사용하는 방법도 있다.
Object.assign(dest, [src1, src2, src3...])
첫번째 인수 dest는 목표로 하는 객체이며
인수들은 복사하고자 하는 객체이다. 이후 이 객체들의 프로퍼티를
dest에 복사하며 마지막으로 dest가 반환된다.
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// permissions1과 permissions2의 프로퍼티를 user로 복사합니다.
Object.assign(user, permissions1, permissions2);
// now user = { name: "John", canView: true, canEdit: true }
만약 목표하는 객체에 동일한 이름을 가진 프로퍼티가 있다면 덮어씌우게 된다.
let user = { name: "John" };
Object.assign(user, { name: "Pete" });
alert(user.name); // user = { name: "Pete" }
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
이 메서드를 사용하면 반복문 없어도 간단하게 복사 가능
중첩 객체 복사
위에선 user의 모든 프로퍼티가 원시값일 경우만 가정했지만 프로퍼티가
다른 객체에 대한 참조 값일 경우도 있다.
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true, 같은 객체입니다.
// user와 clone는 sizes를 공유합니다.
user.sizes.width++; // 한 객체에서 프로퍼티를 변경합니다.
alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인할 수 있습니다.
clone.sizes = user.sizes로 프로퍼티를 복사하는 것만으론 객체를 복제할 수 없다.
user.sizes는 객체이기에 참조 값이 복사되기 떄문인데
이렇게 되면 clone.sizes = user.sizes 똑같은 값을 공유하게 된다
이 문제를 해결하려면 user[key]의 각 값을 검사하면서, 그 값이 객체인 경우
객체의 구조도 복사해주는 반복문을 사용해야한다. 이런 경우를 '깊은 복사(deep cloning)'이라고 한다.
깊은 복사 시 사용되는 표준 알고리즘인 Structured cloning algorithm 을 사용하면 위 사례를 비슷한
다양한 상황에서 객체를 복제할 수 있습니다.
JS 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용하면
이 알고리즘을 직접 구현하지 않고도 깊은 복사를 처리할 수 있으므로 참고하자
'JavaScript > Basics' 카테고리의 다른 글
[JavaScript] 메서드와 this (0) | 2022.03.11 |
---|---|
[JavaScript] 가비지 컬렉션 (0) | 2022.03.11 |
[JavaScript] 폴리필 - Polyfills and transpilers (0) | 2022.03.10 |
[JavaScript] 테스트 자동화 (0) | 2022.03.10 |
[JavaScript] 화살표 함수 기본 (0) | 2022.03.10 |