오늘은 자바스크립트의 객체 부분의 참조에 의한 객체 복사에 대해 공부하겠습니다.
모던 자바스크립트 튜토리얼을 참고하였습니다.
객체와 원시 타입의 근본적인 차이 중 하나는 객체는 '참조에 의해(by reference)' 저장되고 복사된다는 것이다.
원시값(문자열, 숫자, 불린 값)은 '값 그대로' 저장, 할당되고 복사되는 반면에 말이다.
예시를 실행하면 두 개의 독립된 변수에 각각 문자열 "Hello!"가 저장된다.
그런데 객체의 동작방식은 이와 다르다.
변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소'인 객체에 대한 '참조값'이 저장된다.
그림을 통해 변수 user에 객체를 할당할 때 무슨 일이 일어나는지 알아보자.
객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 '참조'할 수 있는 값이 저장된다.
따라서 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않는다.
변수는 두 개지만 각 변수엔 동일 객체에 대한 참조 값이 저장된다.
따라서 객체에 접근하거나 객체를 조작할 땐 여러 변수를 사용할 수 있다.
객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있다. 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두 개인데, 그중 하나(admin)를 사용해 서랍장을 열어 정돈한 후, 또 다른 열쇠로 서랍장을 열면 정돈된 내용을 볼 수 있다.
참조에 의한 비교
객체 비교 시 동등 연산자 ==와 일치 연산자 ===는 동일하게 동작한다.
비교 시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환한다.
두 변수가 같은 객체를 참조하는 예시를 살펴보자. 일치, 동등 비교 모두에서 참이 반환된다.
obj1 > obj2 같은 대소 비교나 obj == 5 같은 원시값과의 비교에선 객체가 원시형으로 변환된다. 객체가 어떻게 원시형으로 변하는지에 대해선 곧 학습할 예정인데, 이러한 비교(객체끼리의 개소 비교나 원시값과 객체를 비교하는 것)가 필요한 경우는 매우 드물긴하다. 대개 코딩 실수 때문에 이런 비교가 발생한다.
방법은 있는데 자바스크립트는 객체 복제 내장 메서드를 지원하지 않기 때문에 조금 어렵다. 사실 객체를 복제해야 할 일은 거의 없다. 참조에 의한 복사로 해결 가능한 일이 대다수이다.
정말 복제가 필요한 상황이라면 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순화해 원시 수준까지 프로퍼티를 복사하면 된다.
Object.assign를 사용하는 방법도 있다.
문법과 동작방식은 다음과 같다.
assign 메서드를 사용해 여러 객체를 하나로 병합하는 예시를 살펴보자.
목표 객체(user)에 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값이 덮어씌워 진다.
Object.assign을 사용하면 반복문 없이도 간단하게 객체를 복사할 수 있다.
예시를 실행하면 user에 있는 모든 프로퍼티가 빈 배열에 복사되고 변수에 할당된다.
clone.size = user.size로 프로퍼티를 복사하는 것만으론 객체를 복제할 수 없다.
user.size는 객체이기 때문에 참조 값이 복사되기 때문이다. clone.size = user.size로 프로퍼티를 복사하면 clone과 user는 같은 size를 공유하게 된다.
이 문제를 해결하려면 user[key]의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야한다. 이런 방식을 '깊은 복사(deep cloning)'라고 한다.
깊은 복사 시 사용되는 표준 알고리즘인 Structured cloning algorithm을 사용하면 위 사례를 비롯한 다양한 상황에서 객체를 복제할 수 있다.
자바스크립트 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용하면 이 알고리즘을 지접 구현지 않고도 깊은 복사를 처리할 수 있으므로 주의해야한다.