primitive type & object/reference type & immutable (불변성)

김승ㅈIT·2023년 2월 15일
post-thumbnail

원시 타입과 객체/참조 타입

자바스크립트에서 데이터 타입을 원시 타입과 객체 타입으로 구분하는 이유는 무엇일까?

  • 원시 타입의 값, 즉 원시 값은 변경 불가능한 값이다. 이에 비해 객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다.
  • 원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장된다. 이에 비해 객체를 변수에 할당하면 변수 (할당된 메모리 공간)에는 참조 값이 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 잔달된다. 이를 값에 의한 전달 (Pass By Value)이라 한다. 이에 비해 객체를 가라키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달(Pass By reference)이라 한다.

    원시 타입과 객체 타입은 데이터 저장방식과 할당 방식이 다르기 때문에 구별이 필요함을 알 수 있다.


메모리에 할당되는 방식

원시 타입과 깊은 복사(deep copy)

위 사진은 var score 변수를 선언하고 score = 80 변수 할당했을 때 메모리 할당 방식이다. 변수의 선언과 동시에 할당 var score = 80을 했을 경우도 초기 변수 값은 undefined로 초기화 된다.

var 키워드를 사용한 변수 선언문 이전에 변수를 참조하면 변수 호이스팅에 의해 undefined로 평가된다.
정리하자면 자바스크립트에 의해 변수가 호이스팅되어 undefined로 메모리에 저장되어 있다가 변수가 할당되는 시점에서 새로운 메모리 주소에 대한 리터럴를 갖는 것이다.

score = 90 으로 변수를 재할당 했을 경우 기존의 리터럴 값이 바뀌는 것이 아닌 새로운 주소를 가진 리터럴를 생성한다. 이것이 원시 타입(primitive type)의 메모리 할당 방식이다.

var score = 80;
var copy = score;

console.log(score,copy) //80,80

위 코드와 같이 변수 score의 값을 할당 받은 변수 copy는 score와 같은 값을 출력하게 될 것이다. 하지만 변수 score의 값을 재할당하여 변경한다면 어떻게 될까?

var score = 80;
var copy = score;

console.log(score,copy) //80,80

var score = 100;

consoel.log(score,copy) //100,80

변수 score의 값을 재할당하여 변경하였을 경우 원시 타입은 메모리를 할당하여 새로운 메모리 주소값에 대한 리터럴 값을 저장하고 변수 copy는 값에 의한 전달 (Pass By value) 방식으로 score의 값 자체(원시 값)를 전달 받으므로 서로의 변수의 값에 대해 어떠한 영향도 주지 않는다. 이것을 깊은 복사 (deep copy)라고 한다.

객체 타입과 얕은 복사(shallow copy)

객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있다. 원시 값을 할당한 변수를 참조하면 메모리에 저장되어 있는 원시 값에 접근한다. 하지만 객체를 할당한 변수를 참조하면 메모리에 저장되어 있는 참조 값을 통해 실제 객체에 접근한다.

var person = {
  name: 'Lee'
};

console.log(person); // {name: "Lee"}

위 사진과 코드로 설명하면 person이라는 변수는 객체를 할당했다. 할당이 이뤄지는 시점에 객체 리터럴이 해석되고, 그 결과 객체가 생성된다. 변수 person에 저장되어 있는 참조 값(주소 값) 위 예시로는 104번 주소로 실제 객체에 접근하는 것이다.

객체 타입은 변경 가능한 값이다. 따라서 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 즉, 재할당 없이 프로퍼티를 동적으로 추가할 수도 있고 프로퍼티 값을 갱신할 수도 있으며 프로퍼티 자체를 삭제할 수도 있다.

var person = {
  name: 'Lee'
};

pserson.name = 'Kim';
person.number = '0615';

console.log(person); // {name: "Kim", number: "0615"}

객체를 변경할 때마다 원시 값처럼 이전 값을 복사해서 새롭게 생성한다면 명확하고 신뢰성이 확보되겠지만 객체는 크기가 매우 클 수도 있고, 원기 값 처럼 크기가 일정하지도 않는 등 메모리의 효욜적인 소비가 어렵고 성능이 나빠진다.
따라서 메모리를 효율적으로 사용하기 위해 객체는 변경 가능한 값으로 설계되어 있다.
객체는 이러한 구조적 단점에 따른 부작용이 있다. 그것은 원시 값과는 다르게 여러 개의 시별자가 하나의 객체를 공유할 수 있다는 것이다.

var person = {
  name: 'Lee'
};

var copy = person;

객체를 가리키는 변수 person를 변수 copy에 할당하면 원본인 person의 참조 값 (주소 값)이 복사되어 전달된다. 이를 참조에 의한 전달(Pass by reference)이라고 하며 얕은 복사(shallow copy) 방식이다.

var person = {
  name: 'Lee'
};

var copy = person;

copy.name = 'Kim';
copy.address = 'Jeju';

console.log(person); // { name: "Kim", address: "Jeju"}
console.log(address); // { name: “Kim:, address: “Jeju”}

이것은 두 개의 식별자가 하나의 객체를 공유한다는 것을 의미한다. 따라서 원본 또는 사본중 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다.


불변성 (immutable)

위 내용들을 정리해보자면 변수를 선언하면 자바스크립트는 메모리에 해당 변수를 통해 접근할 수 있는 메모리 공간을 마련한다. 즉 변수라는 것은 메모리에 저장되어 있는 어떠한 값에 접근하는 일종의 단축어 같은 개념의 식별자이다. 식별자는 어떤 값이 저장되어 있는 메모리 주소를 기억한다. 만약 변수가 존재하지 않는다면 직접 메모리 주소를 사용하여 메모리에 값을 저장할 공간을 마련하고 값을 저장하거나 접근해야하는 불편함을 겪는 것이다. 메모리에 저장하는 방식에 따라 원시 타입과 객체(참조)타입으로 나누며 참조에 의한 전달 방식과 값에 의한 전달 방식이 어떻게 동작하는지 알아봤고 불변성에 대해 알아보자.

불변성
: 상태를 변경하지 않는 것으로 정의할 수 있다.

React에서 불변성이 중요한 이유

Javascript 엔진은 call stack과 heap memory 2가지 메모리 공간을 가지고 있다.

call stack
실행 중인 함수를 추적해 계산을 수행하고 지역변수를 저장하는 공간입니다. 이곳에 원시 타입들이 저장됩니다.

hip memory
참조 타입들이 할당되는 곳입니다. 메모리 누수를 방지하기 위해 js 엔진의 메모리 관리자가 항상 관리하는 공간입니다.

사실 메모리 구조를 조금 더 자세하게 살표보면 이러한 형태인 것이다.
변수 a에 값 10을 저장을 했을 경우 원시타입이므로 콜 스택의 변수값에 10이 그대로 저장되고 변수 b,c,d에 array, object를 저장할 경우 객체(참조)타입이므로 실제 값은 메모리 힙에 저장되고, 메모리의 힙의 주소가 콜 스택의 값에 저장이 되는 것이다.

불변성을 지킨다는 의미는 “콜 스택 메모리 주소의 값을 변경할 수 없게 한다”라는 의미이다. 왜냐하면 React는 콜 스택의 주소값만을 비교해 상태 변화를 감지하는데, 이를 얕은 비교라고 합니다. 이것이 리액트의 state를 빠르게 감지할 수 있는 장점이자 불변성을 유지해야 하는 이유이다. 즉, usestate hook이나 redux를 사용한 상태 관리를 위해 불변성이 중요한 것이다.
원시 타입은 변수에 대한 값을 재할당할 경우 새로운 메모리에 할당되어 콜 스택의 주소 값(리터럴)이 새로운 주소의 리터럴 값을 가지게 되어 상태 변화에 감지가 되지만 객체(참조)타입의 경우 리터럴 값만 변경하면 실제로 콜스택의 주소값은 변경이 없어 state 감지가 되지 않아 리렌더링이 되지 않는 것이다. 그래서 저희가 spread 연산자를 쓰고, immer 라이브러리를 사용해 새로운 배열과 오브젝트를 만들어 반환하는 이유이다.

참고 자료
https://tang-co.tistory.com/141
https://evan-moon.github.io/2020/01/05/what-is-immutable/
https://narup.tistory.com/268

0개의 댓글