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

학미새🐥·2023년 4월 13일
0

4-2

원시형(primitive)과 다른 객체형의 특징
=> 참조에 의해 저장되고 복사된다
(primitive는 '값' 그대로 저장,복사됨)

변수에 객체를 담을 땐,
객체 내부의 내용이 변수에 담기는 것이 아닌,
객체가 저장된 메모리 주소, 객체의 참조 값
이 변수에 담긴다.

let user = { name: "John" };

let admin = user;

위와 같은 예시에서 다음과 같이 실행된다.
1. {name : "John"} 이라는 객체가 메모리에 저장됨.
(그 메모리 주소(참조값)는 abc라고 하자.)
2. 변수 user에 abc가 담긴다
3. 변수 user에 담긴 abc가 복사되어 변수 admin에 담긴다.

이 원리를 잘 이해해야만 다음의 예제가 이해될 것이다.

let user = { name: 'John' };

let admin = user;

admin.name = 'Pete'; 

alert(user.name); // 'Pete'가 출력됨.

adminname을 Pete로 설정한 것인데,
username을 출력했더니 왜 Pete가 나올까?
위에서 처음 생성해준대로 John이 나와야하지 않을까?

이것이 바로 '값 그대로의 복사'와 '참조'의 차이이다.

앞선 설명에서 user 변수와 admin 변수 모두
{name:'John'} 객체의 메모리주소(참조값)인 abc를 담고 있다고 했다.
그럼

//1번
admin.name = 'Pete';

해당 코드가 실행되면,
admin 변수가 담고있는 abc 값의 주소를 찾아가, 그 abc 메모리에 저장되어있는 객체 내부의 name 의 값을 'Pete'로 변경한다.

//2번
alert(user.name);

해당 코드가 실행되면,
user 변수가 담고있는abc 값의 주소를 찾아가, 그 abc 메모리에 저장되어있는 객체 내부의 name의 값을 읽어온다.
이 때, abc 메모리에 저장되어있는 객체에서 name의 값은 1번 코드가 실행됐을 때 'Pete'로 설정되었기 때문에 Pete가 get 되는 것이다.


참조에 의한 비교

  • 객체를 비교할 땐 ==, === 모두 동일하게 동작한다.
  1. 두 변수가 같은 객체를 참조할 때
let a = {};
let b = a; // 참조에 의한 복사

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

a와 b 모두 빈 객체{}의 메모리 주소(참조값)을 가지기 때문에
동일한 객체를 참조하고 있으므로 비교 시 모두 true이다.

  1. 내용이 같지만 각각 독립된 객체를 가리킬 때
let a = {};
let b = {}; // 독립된 두 객체

alert( a == b ); // false

a와 b가 가리키는 두 객체는 서로 동일한 내용, 즉 빈 객체이지만, 다른 메모리 주소(참조값)을 가지고 다른 공간에 저장되어있는 독립된 객체이기 때문에 다른 것으로 취급된다.

객체 복제와 병합

복제

참조값을 복사하는 것이 아닌, 객체를 복제 즉 객체의 내용 그대로를 복사하여 독립된 새로운 객체를 만들고자 할 땐 어떻게 할까?

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

let clone = {}; // 독립된 빈 객체 생성 

// user 객체의 각 프로포티의 원시값들을 하나하나 복사하여 추가한다. 
for (let key in user) {
  clone[key] = user[key];
}

이렇게 복제할 경우, user 객체와 clone 객체는 별도의 객체가 되고,
따라서 user 객체의 속성을 변경하여도, 이것이 clone 객체에 적용되지 않는다.

user.name = "Pete"; // user 객체의 name 값 변경

alert( clone.name ); // John 출력

user 객체의 name 값을 변경해도, 그것이 clone 객체의 name 값엔 아무런 영향을 끼치지 않기 때문에 원래 값인 John이 출력된다.

복제와 병합 : Object.assign

메소드를 활용하여도 객체의 복제가 가능하다.

  • 형태 :
Object.assign(목표 객체, [복제 대상 객체1, 복제 대상 객체2, 복제 대상 객체3])

위와 같은 형태처럼

  • 목표 객체 1개,
  • 복제하고자 하는 객체 여러개

가 가능하기 때문에,

  • 복제하고자 하는 객체가 1개 -> 그대로 clone
  • 복제하고자 하는 객체가 여러개 -> 여러 객체를 병합

이 되는 것이다.

또 이때, 목표 객체는 빈 객체일 필요 없다.

let user = { name: "John" };	//목표 객체 user
let permissions1 = { canView: true };	//복제 대상 객체 1
let permissions2 = { canEdit: true };	//복제 대상 객체 2

// permissions1과 permissions2의 프로퍼티를 user로 복사합니다.
Object.assign(user, permissions1, permissions2);

위의 코드가 실행되면
user 객체는
기존 properties + permissions1의 properties + permissions2의 properties
={ name: "John", canView: true, canEdit: true }

가 된다.

  • 만약 복제 대상 객체와 목표 객체의 키가 겹친다면, 목표 객체 키의 값이 복제 대상 객체 키의 값으로 덮어쓰기 된다.
  • 메소드를 활용해서 한줄로 객체를 복제할 수 있다.

Object.assign({},복제 대상 객체);

중첩 객체 복사

property 값이 원시값이 아닌 또다른 객체라면?

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

이럴 경우, sizes 키의 값도 {height:182, width:50} 이라는 객체의 참조값을 담는다.

따라서

clone.name = user.name;

이 경우엔 clone이라는 별도의 객체에 name: "John"이 담기게 될 것이다.

하지만

clone.sizes = user.sizes;

의 경우, user sizes가 담고 있는, {height:182, width:50} 이라는 객체참조값이 clone 객체의 sizes 키의 값으로 복사되는 것이기 때문에,
clone과 user 각각의 sizes 는 모두 같은 {height:182, width:50} 이라는 객체를 가리키게 되는 것이다.

따라서 객체 내부의 객체 내용까지 모두 복제하고자 할 경우엔,
user 객체의 각 property를 복사하는 반복문 내부에서
객체를 만날 경우 또한번의 반복문으로 user 객체의 property 객체 내부의 property 까지 일일이 복사해야 완벽한 clone이 가능하다.

💡 이렇게 객체 내부의 객체 property까지 모두 그대로 복제하는 것을 "deep cloning (깊은 복사)" 라고 한다.


🙉 오늘의 TL;DR

  • 객체는 참조에 의해 저장되고 복사된다.
  • 따라서 일반적인 대입 연산자 = 를 사용하여 변수에 객체를 대입하면, 값(내용)이 담기는 것이 아닌, 해당 객체의 메모리 주소가 담긴다.
  • 객체의 내용을 새로운 객체로 복붙하는 메소드로 Object.assign 이 있다.
profile
뭐든 다해보려는 공대생입니다

0개의 댓글