본문 바로가기

JavaScript

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

 

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

 

let message = "Hello!";
let phrase = message;

 

위 코드를 실행하면 두 개의 독립된 변수에 각각 문자열 "Hello"가 저장된다.

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

 

let user = {
  name: "John"
};

 

객체는 메모리 내 어딘가에 저장되고, 변수 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

 

두 코드를 보면 위 코드는 객체를 복사하여 ==, ===값이 둘다 true가 나왔고, 밑 코드는 두 객체 모두 비어있다는 점에서 같아 보이지만, 독립된 객체이기 때문에 일치/동등 비교하면 flase가 반환된다.

 

- 객체 복사, 병합과 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);

 

- 중첩 객체 복사

 

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

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

 

이런 경우엔 어떻게 복제할 수 있을까? clone.sizes = user.sizes로 프로퍼티를 복사하는 것만으로는 객체를 복제할 수 없다. use.sizes는 객체이기 때문에 참조 값이 복사되기 때문이다. clone.sizes = user.sizes로 프로퍼티를 복사하면 clone 과 user는 같은 sizes를 공유 하게 된다.

 

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, 다른 객체에서 변경 사항을 확인할 수 있습니다.

 

이 문제를 해결하려면 user[key]의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 한다. 이런 방식을 deep cloning이라고 한다.

'JavaScript' 카테고리의 다른 글

[JavaScript] defer, async 스크립트  (0) 2022.05.14
[JavaScript] 메서드와 this  (0) 2022.04.27
[JavaScript] 객체  (0) 2022.04.26
[JavaScript] 화살표 함수  (0) 2022.04.24
[JavaScript] 함수 표현식  (0) 2022.04.24