[JS] Primitive, Reference 데이터 복사

colki·2021년 4월 25일
1

Primitive & Reference

Primitive (원시형 데이터)
숫자, 문자열, null, Boolean, undefined, symbol

Reference (참조형 데이터)
객체, 배열, 함수


원시형 데이터는 기존의 메모리 공간을 그대로 활용한다. 만약 값을 바꾸는 경우 메모리에 이미 저장된 동일한 값이 있는 지 찾아보고 있다면 재사용하고, 없다면 새로운 공간에 값을 저장하여 사용한다.
그래서 원시형은 기존 값을 변경해서 사용하는 것이 아닌, 새로 만드는 동작을 통해서만 값을 할당할 수 있기 때문에 Primitive는 불변값이라 한다.
이 값은 가비지 컬렉터가 수거하지 않는 한 영원히 변하지 않는다.

반면에 참조형 데이터는 변수를 할당하는 과정에서 큰 차이가 있는데,
참조형은 원시형과 달리 객체의 변수(프로퍼티)영역이 별도로 존재한다.
객체의 프로퍼티를 저장하는 영역이 별도로 분리되어 있기 때문에 값에 바로 접근하는 것이 아니라, 별도의 객체만의 공간에 저장하되 저장된 주소를 참조하는 형태로 동작한다.

Primitive: 값이 담긴 주소값을 바로 복제
Reference: 값이 담긴 '주소값들로 이루어진 묶음'을 가리키는 주소값을 복제

주소는 한 번 할당하면 그 주소는 변경되지 않는다. 그런데도 참조형을 가변값이라 하는 이유는 주소는 그대로이지만 주소가 바라보고 있는 실제값은 변경이 가능하기 때문이다.
참조형 데이터는 이런 reference형태로 주소값을 주고 받는 특성상 객체의 레퍼런스를 참조한 다른 객체에서 레퍼런스를 변경했을때 의도치 않게 원본 레퍼런스가 변경될 수 있다.


🙄 주소를 참조하는 부분에 대해서 비유를 들어 설명한다면, 원시형 참조형 둘다 변수에 값을 할당하면 자신의 값이 위치한 주소가 담긴 쪽지를 받는다. 원시형은 자신의 주소가 담긴 쪽지를 보고 찾아가면 바로 자신의 값을 확인할 수 있지만, 참조형은 쪽지를 보고 찾아가면 또 쪽지가 있다.

'잘 찾아왔는가 자네. 하지만 당신의 짝은 여긴 없다네 다시 이 주소를 보고 찾아가면 된다네'


참조형 데이터 프로퍼티 복사

위와 같은 특성 때문에, 변수를 복사할 때 변수에 할당하는 데이터 타입이 Primitive이냐 Reference이냐에 따라 다른 결과가 나타난다.

let a = 'colki';
let b = a;

let person = { name: 'hany', age: 20 };
let person2 = person;

원시형과 참조형을 복사했을 때는 다음과 같이 메모리에 저장된다.


사실 둘다 변수에 함께 저장하는게 아니라 주소값을 가지는 과정이 있기 때문에 엄밀히 따지면 원시형도 참조형데이터이긴 하지만, 데이터 할당 과정에서 차이가 있으므로 변수 복사 이후에
값을 재할당하는 과정에서 큰차이가 생긴다.

let a = 'colki';
let b = a;

let person = { name: 'hany', age: 20 };
let person2 = person;

/*** 재할당 ***/

b = 'coldKiwi';
person2.age = 30;
console.log(person) // {name: "hany", age: 30}


b ='coldKiwi'로 재할당했을 때 b가 가리키는 주소가 510에서 511로 변경되었다.
'coldKiwi'를 저장하는 공간을 새로 만들고 b가 그 영역을 가리키게끔 한 것을 알 수 있다.
원시형은 값만을 따로 바꿀 수는 없고 새로 만드는 동작을 통해서만 변경이 가능하다.
그렇기 때문에 원시형을 불변값이라고 칭하는 것이다.

반면 객체 person이 가리키는 주소는 그대로이지만 객체의 데이터영역에서 프로퍼티가 가리키는 주소값이 514에서 515로 바뀌어서 결국 객체 내부의 프로퍼티의 value가 바뀌었다.

원시형은 주소값을 복사하는 과정이 한 번만 이루어지지만, 참조형은 단계를 더 걸치기 때문에
원시형처럼 새로운 객체로 할당한 것이 아니라, 객체의 데이터영역에서 주소값을 바꿔서 변수의 데이터영역에서의 값이 바뀐 걸 알 수 있다.

둘 다 주소와 값이 바뀌었는데 객체를 두고 가변적이다 하는 이유는 바로 변수자신이 가리키는 주소값에 있다. 이부분이 굉장히 헷갈렸는데 처음과 끝만 두고 본다면

결과적으로 원시형은 주소 자체가 다르므로 변경이 아니라
새로이 할당해서 직접 값을 변경한 것이지만,

참조형은 주소는 동일하다. 그러므로 객체는
주소가 똑같은데 값만 바꿀 수 있다고 말하는 것이다.
이렇게 객체의 프로퍼티를 변경할 경우에는 주소는 그대로, 기존 객체 내부의 값만 바뀐다.
obj.property obj[property] 이렇게 프로퍼티에 접근해서 레퍼런스를 바꾸는 경우에 해당된다.

참조형의 데이터 자체를 변경하는 경우

객체의 프로퍼티를 변경하는 것이 아니라 원시형 처럼 통째로 재할당한다면 어떻게 될까?

let person = { name: 'hany', age: 20 };
let person2 = person;

person2 = {name: 'hachii', age: 18};
console.log(person) // {name: "hany", age: 20}

원시형처럼 새로운 객체를 할당함으로써 값을 직접적으로 변경했다.
이 경우 새로운 주소값을 할당해주는 것이기 때문에, 객체의 데이터영역이 별도로 새로 생성된다. 이 때는 값을 직접적으로 수정했기 때문에 주소도 바뀌어버린 것이다.

즉 참조형 데이터가 가변값이라고 말하는 것은, 참조형 데이터 자체를 변경하는 것이 아니라
내부의 프로퍼티를 변경하는 경우 성립한다.

이것은 전에 포스팅했던 fill메서드에서의 객체리터럴, 배열리터럴로 채울 때에 상황과 동일하다. 역시 객체의 주소값을 모든 요소들이 참조하므로 프로퍼티를 변경하면 다같이 주소가 변경되고, 직접 값을 새로 할당할 때에 첫 번째 요소만 바뀌는 것이 바로 이 원리였다.
그때는 대강 그렇구나 하고 넘어갔었는데 이번 기회로 확실하게 알았다. 😁

const array8 = Array(3).fill({name: 'colki'});

** [0]의 메모리주소값에 연결된 배열안의 요소들이 다같이 변경됨 **
array8
// [{…}, {…}, {…}]
0: {name: "colki"}
1: {name: "colki"}
2: {name: "colki"}

array8[0].age = 20;

array8
// [{…}, {…}, {…}]
0: {name: "colki", age: 20}
1: {name: "colki", age: 20}
2: {name: "colki", age: 20}
length: 3
__proto__: Array(0)


array8[0].name = 'hany'

array8
// [{…}, {…}, {…}]
0: {name: "hany", age: 20}
1: {name: "hany", age: 20}
2: {name: "hany", age: 20}

** [0]만 변경 **
array8[0] = {name: "hani", gender:'female'} 

array8
// [{…}, {…}, {…}]
0: {name: "hani", gender: "female"}
1: {name: "hany", age: 20}
2: {name: "hany", age: 20}

Reference 코어 자바스크립트, ColkiVelog_ fill메서드

profile
매일 성장하는 프론트엔드 개발자

1개의 댓글

comment-user-thumbnail
2021년 11월 1일

똑똑한 분들이 참 많다는 걸 느끼고 갑니다.
좋은 글 감사합니다.

답글 달기