객체와 원시 타입의 근본적인 차이 중 하나로 저장, 복사 방식이 있다.
원시 타입은 값이 그대로 저장, 할당되고 복사된다. 반면, 객체는 ‘참조에 의해’ 저장되고 복사된다.
원시 타입을 먼저 보자.
let message = "Hello!";
let phrase = message;
이를 실행하면 message, phrase 라는 각각의 독립된 변수에 문자열 “Hello!” 가 저장된다.
그런데 객체의 동작 방식은 이와 다르다. 변수에 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 ‘메모리 주소'인 객체에 대한 ‘참조 값' 이 저장된다.
let user = {
name: "John"
};
객체는 메모리 내 어딘가에 저장되고, 변수 user 에는 객체를 ‘참조'할 수 있는 값, 즉 객체가 저장되어있는 ‘주소값'이 저장된다.
따라서 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
대소 비교를 하거나 원시값과 비교할 때는 객체가 원시형으로 변환된다. 그러나 이러한 비교가 이루어지는 경우는 매우 드물다.
객체를 복제하고 싶다면 어떻게 해야 할까?
자바스크립트는 객체 복제 내장 메서드를 지원하지 않기 때문에 조금 어렵다. 정말 복제가 필요한 상황이라면 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회해 원시 수준까지 프로퍼티를 복사하면 된다.
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...)
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 }
// user 객체에 새 객체들의 프로퍼티가 추가된다.
목표 객체에 동일한 이름을 가진 프로퍼티가 있는 경우에는 기존 값이 덮어씌워진다.
let user = { name: "John" };
Object.assign(user, { name: "Pete" });
alert(user.name); // user = { name: "Pete" }
user 객체를 빈 객체에 복사한 뒤 새 변수에 할당하려면 다음과 같이 하면 된다.
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
프로퍼티가 다른 객체에 대한 참조값일수도 있다. 다음과 같은 경우가 해당된다.
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); // 182
clone.sizes = user.sizes 처럼 프로퍼티를 복사하는 것만으로는 객체를 복제할 수 없다. user.sizes 는 객체이기 때문에 참조 값이 복사되기 때문이다.
이 문제를 해결하려면 user[key] 의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조까지 복사해주는 반복문을 사용해야 한다. 이런 방식을 ‘깊은 복사'라고 한다.
깊은 복사 시 사용되는 표준 알고리즘인 Structured clonind algorithm 을 사용하면 위 사례를 비롯한 다양한 상황에서 객체를 복제할 수 있다.
자바스크립트 라이브러리 lodash 의 매서드인 _.cloneDeep(obj) 를 사용하면 이 알고리즘을 직접 구현하지 않고도 깊은 복사를 처리할 수 있다.