본문 바로가기
경일/Javascript

[Javascript] 객체의 비교와 복사

by dev_kong 2022. 1. 7.
728x90
728x90

0.목차

1. 개요

2. premitive 와 reference 가 메모리에 저장 되는 방식

2. 얕은복사

3. 깊은복사

1.개요

const a = { num: 1 };
const b = { num: 1 };
console.log(a === b);

 

a라는 객체와 b라는 객체를 만든 뒤에

동일한 프로퍼티를 생성해 주었다.

두개를 비교한 값을 출력해보자.

const a = { num: 1 };
const b = { num: 1 };
console.log(a === b); // false

분명 우리눈에는 똑같이 생겼는데,

false가 출력 된다.

이걸 이해하기 위해서

메모리에 데이터를 저장하는 방법을 조금 알아볼 필요가 있다.

 

2. premitive 와 reference 가 메모리에 저장 되는 방식

premitive type이 메모리에 저장되는 방식

그림을 그려야 되나 고민했는데  밑의 블로그에서 그냥 따왔다.

https://devwhkang.gatsbyjs.io/posts/let-const-memory/

 

위의 그림을 코드로 표현해보면

let number = 100;

let 또는 const로 변수를 선언해주고

값을 할당해주면,

메모리의 한 공간에 100 이라는 값이 들어간다.

이 메모리의 모든 공간에는 각각의 주소값이 존재하는데,

number 변수는 주소값을 이용해 값에 접근한다.

 

number = 200;

그럼 number 라는 변수에 값을 재할당 해주면

메모리상에서는 어떤 일이 일어날까.

100 이라는 숫자가 지워지고,

그 자리에 200 이 들어갈 것 같지만 그렇지않다.

 

메모리의 새로운 공간에 200 이라는 값을 넣어두고,

number는 새로운 공간을 가리키게 된다.

그림으로 보면 이렇게 된다.

 

ㅎ.... 결국 그림...

기가막힌 그림실력이다.

 

메모리상에서 저런 동작이 가능한 것은

number가 let 을 이용해 선언 되었기 때문이다.

const로 선언된 변수라면,

가리키는 값을 변경하는 것이 불가능하다.

우릴 이건 const는 값의 재할당이 불가능하다. 라고 간략하게 표현하는 것이다.

 

* 저기 참조가 되지않는 메모리공간에 할당된 값(=100)은 garbage 값이라고 하고

자바스크립트 내부에 있는 grabage collector 에 의해 초기화 된다.

 

그럼 premitive data가 이렇게 되는건 알겠는데,

reference data는 어떤 방식을 저장 되는 걸까.

 

사실 메모리는 두개의 공간으로 나눠져 있다.

하나는 premitive를 저장하는 공간,

하나는 reference를 저장하는 공간

 

작성의 용이를 위해

p동 과 r동이 이라고 표현하겠다.

reference data는 r동에 저장된다.

 

const a = { num: 1 };
const b = { num: 1 };

 

맨위에서 사용한 코드를 그림으로 그...려..보자...

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

멋진 그림이다.

각 동의 왼쪽칸에는 해당 공간의 주소를 임의로 표기해봤다.

 

위의 멋진 그림을 보면,

reference data 인 객체는 r 동에 저장 되어있다.

그리고 p동에는 r동의 주소값이 들어있다.

 

즉, 변수  a와 b는 p동에 저장된 r동의 주소값을 통해 데이터를 참조한다.

이제 a객체와 b객체가 같은지 비교를 했을 때 false가 나오는 이유를 알 수 있다.

 

a와 b는 우리가 눈으로 보기에는 같은 값을 갖고 있기에 같다 라는 생각이 들지만,

컴퓨터가 보기에는 다른 주소값을 참조 하고 있기때문에 다르다는 결과를 출력한다.

 

그럼 우리는 같은 주소값을 참조하는 객체, 즉, 완전히 동일한 객체를 만드는 것이 불가능 한걸까?

 

3. 얕은복사

위의 질문의 답은 가능하다. 이다.

완전히 동일한 객체를 만드는 것을 얕은 복사라고 부른다.

 

얕은 복사를 하는 방법은 간단하다.

a 와 b 가 같은 주소값을 참조하도록 만들면 된다.

텍스트로 보면 복잡해 보이지만 코드로 보면 매우 간단하다.

 

let user1 = { name: 'kong' };
let user2;

 

이렇게 있을 때 user2 와 user1을 완전히 동일한 객체로 만들고 싶으면,

 

let user1 = { name: 'kong' };
let user2;

user2 = user1;
console.log(user2);
console.log(user1 === user2); // true

그냥 user2를 user1으로 할당해주면 된다.

이걸 그림으로 보면,

 

이런 식으로 된다.

그럼 우리는 한가지 추측을 해볼 수 있다.

만약 user2 가 const로 선언되었다면,

얕은복사가 불가능 한거 아닐까?

 

let user1 = { name: 'kong' };
// const로 변수를 선언한 뒤, 값을 할당해 주지 않으면 syntaxError 가 발생하므로 값을 할당함.
const user2 = 1;

user2 = user1; // TypeError: Assignment to constant variable. 에러발생. 아래의 코드는 실행안됨
console.log(user2);
console.log(user1 === user2);

당연히 안된다ㅎ

왜 안되는지 모르겠으면 위에서 부터 다시 천천히 읽어보자.

 

이 방법에는 문제가 한가지 있다.

user1의 값을 변경하면 user2의 값도 변경된다.

let user1 = { name: 'kong' };
let user2;

user2 = user1;
user1.age = 30;
console.log(user2); // { name: 'kong', age: 30 }
console.log(user1 === user2); // true

동일한 주소값을 참조 하기에 당연한 결과다.

만약 내가 처음에는 완전 동일한 객체를 만들고 싶었지만,

나중에 마음이 바뀌어서 user1에만, age를 추가하고 싶다면?

user2에는 age를 죽어도 만들기 싫다면?

그럴때 우린 깊은복사를 해야한다.

3. 깊은복사

사실 깊은복사라는 단어를 처음 들었을 때 부터,

ㅎ.. 깊은복사도 있겠네.. ㅎㅎ

라는 생각을 했다.

그리고 당연하게도 있다.

얕은 복사는 user1 과 user2 가 동일한 프로퍼티를 갖게하고 싶지만,

참조하는 주소값마저 똑같이 하고 싶지 않을때 사용한다.

깊은 복사를 하는 방법에는 두가지가 존재한다.

Object.assign() 메서드를 이용하는 방법과

spread 연산자를 이용하는 방법이다.

 

3-1. Object.assign()

let user1 = { name: 'kong' };
let user2 = {};

Object.assign(user2, user1);
console.log(user2); // { name: 'kong' }
console.log(user2 === user1); // false

코드로 보는 게 간단하다.

Object.assign() 메서드는 두번째 인자의 프로퍼티가 복사되어서 첫번째 프로퍼티에 덮어 씌어진다.

 

이걸 그림으로 보면...

 

이런식으로 된거다.

3-2. spread 연산자 / 문법

그리고 두번째 방법은 spread 연산자를 이용하는 방법이다.

사람에 따라 연산자라고도, 문법이라고도 부르는데

결국 지칭하는 대상은 같다.

나는 처음부터 연산자로 배워서 연산자가 입에 더 잘 붙으니 연산자라고 하겠음.

 

let user1 = { name: 'kong' };
let user2 = { ...user1 };

console.log(user2); // { name: 'kong' }
console.log(user2 === user1); // false

짧다..!

그리고 간단하다!

영어도 아니고!

... 이것만 하면 된다!

...이 spread 연산자이다.

일단 spread 연산자의 정확한 역활은 객체로 감싸져 있는 애들을 객체에서 벗어나게 해준다.

 

예를 들어서..

const numArr = [1, 2, 3, 4, 5];

라는 배열이 있을 때 이 배열의 값들 중 가장 큰 숫자를 출력하고 싶다면,

우리는 for문을 이용할 수 있다.

그런데 spread 연산자와 Math.max() 이용할 수도 있다.

 

const numArr = [1, 2, 3, 4, 5];
console.log(Math.max(...numArr)); // 5

이렇게

 

배열안에 들어있던 1,2,3,4,5 가 배열 밖으로 빠져나오면서 Math.max() 메서드의 인자값으로 들어갔다.

const numArr = [1, 2, 3, 4, 5];
console.log(Math.max(...numArr)); // 5

console.log(Math.max(1, 2, 3, 4, 5)); // 5

두번째 줄의 코드는

바로 아래의 코드와 동일하다.

이걸 통해 ...spread 연산자가 어떤 역활을 하는지는 알게 됐다.

 

위의 코드를 다시 한번 보자.

let user1 = { name: 'kong' };
let user2 = { ...user1 };

console.log(user2); // { name: 'kong' }
console.log(user2 === user1); // false

spread 연산자를 알고보니까 코드가 다르게 보인다.

user1이 객체가 해제되었고 user1의 프로퍼티가 객체에 담겨있는게 보인다.

그래서 user2를 출력했을 때, { name : 'kong' } 가 출력된다.

 

그리고 당연히 user1과 user2는 일치하지 않으므로,

false가 출력되는 것을 확인 할 수 있다.

 

 

 

학원을 다니기 전, 책에서 봤던 내용이다.

별로 안중요한 건줄 알았다.

그래서 빠르게 훑고 지나갔는데,

 

굉장히 중요한 내용이라고 얘기를 해주셔서,

이번엔 까먹지 않으려고 한다.

728x90
728x90

댓글