Study/JavaScript

JavaScript [참조에 의한 객체 복사]

maino77 2021. 9. 2. 21:27

객체와 원시 타입의 근본적인 차이 중 하나는 객체는 '참조에 의해' 저장되고 복사된다는 것입니다.

원시값(문자열, 숫자, 불린값)은 '값 그대로' 저장, 할당되고 복사되는 반면에 말이죠.

// 원시값이 되다는 것을 보여주는 예시
let message = "Hello!";
let phrase = message;

 

객체는 위의 예시와는 다르게 작동합니다.

변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소'인 객체에 대한 '참조 값'이 저장됩니다.

다음 예시를 통해 좀 더 자세히 알아보겠습니다.

let user = {
  name: "John"
};

 

객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 참조할 수 있는 값이 저장됩니다.

따라서 객체가 할당된 변수를 복사할 땐 객체의 참조값이 복사되고 객체는 복사되지 않습니다.

let user = { name: "John" };

let admin = user; // 참조값을 복사함

 

변수는 두 개이지만 각 변수엔 동일 객체에 대한 참조값이 저장됩니다.

따라서 객체에 접근하거나 객체를 조작할 땐 여러 변수를 사용할 수 있습니다.

 

 

참조에 의한 비교

객체 비교시 동등 연산자 == 와 일치 연산자 === 는 동일하게 동작합니다.

비교시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환합니다.

두 변수가 같은 객체를 참조하는 예시를 살펴보겠습니다. 일치, 동등, 비교 모두에서 참이 반환됩니다.

let a = {};
let b = a; // 참조에 의한 복사

alert( a == b ); // true 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true

 

다른 예시를 살펴보겠습니다. 두 객체 모두 비어있다는 점에서 같아 보이지만 독립된 객체이기 때문에 일치, 동등 비교하면 거짓이 반환됩니다.

let a = {};
let b = {}; // 독립된 두 객체

alert( a == b ); // false

 

bj1 > obj2 같은 대소 비교나 obj == 5 같은 원시값과의 비교에선 객체가 원시형으로 변환됩니다. 

 

 

객체 복사, 병합과 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는 목표로 하는 객체입니다.
  • 이어지는 인수 src1, ..., srcN는 복사하고자 하는 객체입니다. ...은 필요에 따라 얼마든지 많은 객체를 인수로 사용할 수 있다는 것을 나타냅니다.
  • 객체 src1, ..., srcN의 프로퍼티를 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 }

 

목표 객체(user)에 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값이 덮어씌워집니다

let user = { name: "John" };

Object.assign(user, { name: "Pete" });

alert(user.name); // user = { name: "Pete" }

 

Object.assign을 사용하면 반복문 없이도 간단하게 객체를 복사할 수 있습니다.

let user = {
  name: "John",
  age: 30
};

let clone = Object.assign({}, user);

 

 

 

중첩 객체 복사

지금까진 user의 모든 프로퍼티가 원시값인 경우만 가정했습니다. 그런데 프로퍼티는 다른 객체에 대한 참조값일 수도 있습니다. 이 경우는 어떻게 해야 할까요?

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

 

clone.sizes = user. sizes 로 프로퍼티를 복사하는 것만으론 객체를 복제할 수 없습니다. user.sizes는 객체이기 때문에 참조값이 복사되기 때문입니다. 그래서 Object.assign 을 이용해 복사합니다.

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, 같은 객체입니다.

 

 

* 요약 *

객체는 참조에 의해 할당되고 복사됩니다. 변수엔 ‘객체’ 자체가 아닌 메모리상의 주소인 '참조’가 저장됩니다. 따라서 객체가 할당된 변수를 복사하거나 함수의 인자로 넘길 땐 객체가 아닌 객체의 참조가 복사됩니다.

그리고 복사된 참조를 이용한 모든 작업(프로퍼티 추가·삭제 등)은 동일한 객체를 대상으로 이뤄집니다.

객체의 '진짜 복사본’을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 Object.assign 을 이용하면 됩니다.

728x90