[JavaScript] 데이터 타입 - 데이터 할당의 동작원리

Happy_Nerd·2020년 12월 2일
5

JavaScript

목록 보기
3/5
post-thumbnail

<코어 자바스크립트>라는 책으로 회사에서 팀원분들과 스터디를 하게 되었고,
책의 첫번째 주제는 데이터 타입이였다.

데이터 타입이라는 제목만 봤을 때는 숫자,문자열,boolean 등
말 그대로 데이터 타입에 대한 내용이 있을 줄 알았는데
그보다는 오히려 데이터가 메모리에 어떻게 저장되고, 복사되는지 등의 내용을 설명하고 있었어서 부제목을 추가해보았다..!

데이터 타입의 종류

자바스크립트의 데이터 타입은 크게 기본형(primitive type)참조형(reference type)으로 나눌 수 있다.

Primitive Type (기본형)

기본형은 불변성을 띈다.
숫자, 문자열, boolean, null, undefined이 있고 ES6에서 Symbol이 추가되었다.

Reference Type (참조형)

참조형은 객체(Object)가 있고 배열, 함수, Date, 정규표현식 등과 ES6에서 추가된 Map, WeekMap, Set, WeekSet 등이 객체의 하위 분류에 속한다.

차이점

기본형값이 담긴 주소값을 바로 복제하는 반면,
참조형은 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제한다.

메모리와 데이터

자바스크립트는 메모리 용량이 과거보다 월등히 커진 상황에 등장해서 c언어나 자바같은 정적 타입의 언어보다 상대적으로 메모리 관리에 대한 압박에 자유로워졌다고 한다.

자바스크립트에서 숫자의 경우 정수형인지 부동소수형인지 구분하지 않고 64비트, 즉 8바이트를 확보한다.

바이트 역시 시작하는 비트의 식별자로 위치를 파악할 수 있기 때문에,
모든 데이터는 바이트 단위의 식별자, 즉 메모리 주소값을 통해 서로 구분하고 연결할 수 있다

식별자와 변수

사실 변수라는 단어는 많이 사용했지만 식별자라는 단어에 대해서는 잘 사용하지 않기도 하고 변수와 다른건가 싶었는데 책에서 설명을 해주고 있었다.

변수 : 변할 수 있는 데이터
식별자 : 어떤 데이터를 식별하는 데 사용하는 이름, 변수명

변수변경 가능한 데이터가 담길 수 있는 공간 또는 그릇이라고 한다면,
식별자그러한 공간을 가리키는 이름이라고 할 수 있다.

변수 선언과 데이터 할당

책에 나온 예시를 사용해서 이해해보자!
사실 책에 그림과 함께 설명이 잘 되어있어서 이해가 아주 쏙쏙 되었다.
뭔가 C언어 배울 때 포인터 나오는 부분 생각나기도 하고..


변수가 선언되고 데이터가 할당되는 과정을 말로 설명해보자면,
  1. (선언) 메모리에서 비어있는 공간을 확보하고 그 공간의 이름을 설정한 후
  2. (할당) 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 데이터를 저장하고 그 주소를 변수 영역에 저장

하는 식으로 이루어진다.

var a = 'yujuck'

책에서는 독자의 이해를 돕기 위해 데이터의 성질에 따라 변수 영역데이터 영역으로 구분해서 설명을 해주었다.

위의 코드를 예로 들면,
1. 변수 영역에 비어있는 공간을 확보해 a라는 이름을 붙인다.
2. 데이터 영역에 yujuck을 저장하고 저장된 공간의 주소값을 변수 영역의 a에 저장한다!

기본형 데이터와 참조형 데이터

불변값

변수상수가 구분되는 기준은 변수 영역 메모리의 변경 가능성이다.
한번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지의 여부에 따라 변수와 상수를 구분 짓는다.
바꿀 수 있으면 변수, 바꿀 수 없으면 상수이다.

변수를 var,let으로 보고 상수를 const로 보면 이해가 확 간다.
const의 경우 맨 처음 선언하고 할당한 후에 나중에 다른 값을 재할당을 하려 하면 에러가 나는 그런 상황을 생각하니 한번에 이해가 됐다.

불변성의 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역이다.
자바스크립트의 기본형 데이터는 모두 불변값이다.

불변값 이라는게 이해가 잘 안갈 수 있는데 이럴 땐 예시가 최고!

var x = 10;
var y = 10;
x = 20;

숫자는 기본형에 속하는 데이터 타입이기 때문에 불변성을 띈다.

1,2번째 줄이 실행되면 밑의 그림과 같이 변수 영역에 x,y가 생기고 10이라는 데이터는 데이터 영역에 저장되면서 x,y가 모두 10의 주소값을 저장하고 있다.

그리고 세번째 줄이 실행되면, x는 데이터 영역에서 20을 찾는데 20이 없기 때문에 새로운 빈 공간에 20을 저장하고 그 20의 주소값을 변수 영역에 있는 x의 값에 저장한다.

10이라는 데이터가 20으로 바뀌는 것이 아니라 20을 위한 새로운 공간을 할당해서 그 공간의 주소값을 가지는 것이다.
그렇기 때문에 불변성을 띈다고 하는 것이다!!
변경은 새로 만드는 동작을 통해서만 이뤄지고,
한번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않는다!

가변값

기본형 데이터가 모두 불변값이라고 했으니 뭔가 참조형 데이터는 모두 가변값일 것 같은 느낌이 드는데..
기본적인 성질은 가변값인 경우가 많지만 설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있다!

참조형 데이터가 변수에 할당되는 과정을 확인해보면서 알아가보자~

var obj = {
  a: 10,
  b: 'yujuck'
}

간단한 객체 하나를 만들어봤다.
기본형과의 차이점은 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점이고,
제일 중요한 포인트는 객체 변수 영역에 값들의 주소값을 가지고 있는 것이 아니라 프로퍼티들을 저장하기 위해 별도로 마련한 영역의 주소를 가지고 있다는 것이다.

  1. 맨 처음 obj를 변수 영역에 만드는 것까지 똑같다.
    그 후 데이터 영역에 데이터를 저장하려고 보니,
    내부가 여러 개의 프로퍼티로 이루어진 데이터 그룹이여서
  2. 이 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고 그 영역의 주소를 저장한다. (@5001에 @7002~? 저장)
  3. @7002, @7003에는 각각의 프로퍼티 이름을 저장하고,
  4. 10'yujuck'을 데이터 영역에서 찾아보고 없기 때문에 @5003, @5004에 저장하고 그 주소값을 각각의 프로퍼티 변수 영역에 저장한다.

값의 주소값을 저장하는건 같지만 크게 다른 점이 위에서 말한 포인트이다.
obj가 직접적으로 @5003, @5004를 가지는 것이 아니라 프로퍼티 그룹의 주소값을 가지고 있는 것이 포인트!!

이렇게 되면 내부 프로퍼티의 값이 변한다해도 obj의 값이 변하는 것이 아니라 프로퍼티가 가진 주소값이 변경된다..

obj.a = 20;

이렇게 값을 변경하면
데이터 영역에 20이 생기고 그 영역의 주소값이 a 프로퍼티 변수 영역의 값으로 들어갈 뿐, obj의 값이 변하지 않는다.

기본형일 때 변경은 새로 만드는 동작에 의해서만 가능했지만
참조형은 새로운 객체가 만들어지는 것이 아니라 기존 객체 내부의 값만 바뀌게 된다.

가비지 컬렉터

자바스크립트는 가비지 컬렉션(GC)이라는 자동 메모리 관리 형식을 활용한다.
가비지 컬렉터의 목적은 메모리 할당을 모니터링하고 할당된 메모리의 블록이 더 이상 필요하지 않은 시점을 확인하여 회수하는 것이다.

어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트 라고 하는데,
참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 되고,
가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거 대상들을 수거한다.

여기서는 간단하게 이 정도로만 소개되고 있다.

변수 복사

결론부터 말한다면 어떤 데이터 타입이든 변수에 할당하기 위해서는 주소값을 복사해야 하기 때문에,
엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터일 수 밖에 없다!는 것이다.

다만 기본형은 주소값을 복사하는 과정이 한번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다는 것이 차이점이다.

예시를 봅시당

var a = 10;
var b = a;

var obj1 = { c: 10, d: 'yujuck' }
var obj2 = obj1;

위에서 설명한 얘기들을 바탕으로 기본형의 복사부터 생각해보자!
ba를 복사하고 있는데,
일단 빈 공간을 확보해 식별자를 b로 지정한다.
그리고 식별자 a를 검색해 그 값을 찾아와야하는데 그 값이 10의 주소값이다.
그 주소값을 가져와 b에 대입시킨다.

기본형은 이렇게 끝!
간단히 말하면 복사하려는 대상이 가지고 있는 주소값(데이터의 주소값)을 복사해온다!


그렇다면 참조형의 복사는..!
obj2도 마찬가지로 일단 변수 영역에 빈 공간을 확보해 obj2라는 식별자를 지정해준 후,
obj1을 검색해 그 값을 찾아와서 obj2에 대입시킨다.

똑같이 복사하려는 대상이 가지고 있는 주소값을 복사해오는데 차이점이 뭐냐!라고 한다면!
위에서 말했듯이 obj1에 저장되어있는 주소값은 실제 값들의 주소값이 아니라
프로퍼티를 저장하기 위해 별도로 마련한 메모리 공간의 주소값을 가지고 있다는 것이다.

var a = 10;
var b = a;
var obj1 = { c: 10, d: 'yujuck' }
var obj2 = obj1;

b = 15;
obj2.c = 20;

복사한 후에 값을 변경하는 상황을 보면 위에 말한 것이 무슨 말이지 알 수 있다

b에 15를 저장하는 과정은,
데이터 영역에서 15를 찾아보고 없으면 빈 공간에 15를 저장한 후 그 주소값을 b에 다시 저장한다.
그러면 a와 b가 가지고 있는 주소값은 달라지게 된다.

obj2.c에 20을 저장하는 과정을 보면,
데이터 영역에서 20을 찾아보고 없으면 빈 공간에 20을 저장한 후 그 주소값을 c 프로퍼티가 저장되어있는 위치로 가져가서 저장한다.
식별자 obj2가 가지고 있는 값이 변경되지 않고 내부 프로퍼티의 값이 변경되었기 때문에 obj1와 obj2가 가지고 있는 값은 아직 그대로이다.

이를 코드로 표현해보자면 다음과 같을 수 있다.

a !== b
obj1 === obj2

만약 obj2 내부의 값을 변경하는 것이 아니라 obj2에 새로운 객체를 대입한다면 기본형과 똑같이 작동한다.

var a = 10;
var b = a;
var obj1 = { c: 10, d: 'yujuck' }
var obj2 = obj1;

obj2 = { c: 20, d: 'yujuck'}

이전 예시와 다르게 프로퍼티를 변경하는 것이 아니라 객체 자체를 새로운 객체로 변경하는 경우에는
새로운 객체가 가지는 주소값으로 변경되기 때문에 값이 변경된다~

즉, 참조형 데이터가 가변값이라고 설명할 때의 가변은 그 내부의 프로퍼티를 변경할 때만 성립한다.

불변 객체

불변 객체는 React, Vue 등의 라이브러리나 프레임워크 뿐만 아니라 함수형 프로그래밍, 디자인 패턴 등에서 매우 중요한 기초가 되는 개념이라고 한다.

어떤 상황에서 불변 객체가 필요한가~ 보면!
값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 있을 수 있다.

이런 상황에서 불변성을 유지하려면 위의 코드처럼 새로운 객체를 계속 재할당하면 되지만 그렇게 하면 비효율적인 코드가 되기 때문에 다른 방법을 사용하는 것이 좋다..!

다른 방법이라면..?

  • 각종 라이브러리 사용하기 (immutable.js, baobab.js 등)
  • spread operator 사용하기
  • Object.assign 사용하기
  • 사용자 정의 함수
  • JSON 사용하기

등의 방법들이 있다!

얕은 복사와 깊은 복사

얕은 복사 : 바로 아래 단계의 값만 복사
깊은 복사 : 내부의 모든 값들을 하나하나 전부 복사

여기서 말하는 복사는 주소값을 가져와서 저장하는 것이 아닌 새로운 객체를 생성해서 재할당하는 것을 말하는데,
얕은 복사를 하게 되면 참조형 데이터 내부에 중첩된 객체가 있을 때 중첩된 객체가 복사되지 않고 주소값만 가져와서 저장하게 되면서 불변성을 유지하지 못하는 상황이 생길 수 있다.
따라서 중접 객체를 복사해야할 때에는 깊은 복사가 필요하다!

Object.assign(), Object.freeze()

const target = { a: 1, b: 'bb'}
const source = { b: 4, c: 10 }

console.log(Object.assign(target, source));
// { a: 1, b: 4, c: 10 }

assign()의 사용 예시인데, assign()메소드는 원본 객체로부터 속성을 복사해올 때 사용하고 복사된 객체를 반환한다.
위의 코드에서 target이 원본 객체, source가 target의 속성을 복사해올 객체가 되고,
source에 없는 a: 1을 복사해오는 것이다.

const obj = {
  prop: 20
};

Object.freeze(obj);
obj.prop = 33;  // strict 모드에서는 error
console.log(obj.prop);  // 42 출력됨

freeze()의 사용 예시이다.
함수 이름처럼 freeze()는 객체를 얼려버려서 새로운 속성을 추가하거나 기존의 속성을 제거하는 것을 막고 값을 변경하는 것 또한 막는다.

위의 두 함수 모두 얕은 복사를 하기 때문에 깊은 복사가 필요한 상항에서는 다른 방법을 사용해야한다.

JSON 사용하기

책에서는 간단하게 깊은 복사를 할 수 있는 방법으로 JSON 문법 사용을 소개하고 있다.

객체를 JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 방법이다.
잘 동작하지만 JSON으로 변경할 수 없는 프로퍼티,
예를 들어 메서드, __proto__ 등의 경우는 모두 무시되기 때문에
순수한 정보를 다룰 때 활용하기 좋은 방법이다!

undefined와 null

undefined

undefined는 사용자가 명시적으로 지정할 수도 있지만 JavaScript 엔진이 자동으로 부여하는 경우도 있다. 다음 세 경우가 이에 해당된다

  • 값을 대입하지 않은 변수에 접근할 때
  • 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
  • return문이 없거나 호출되지 않는 함수의 실행 결과

그런데 값을 대입하지 않은 변수, 이 경우에 대해 배열의 경우 undefined가 아니라 empty라고 해서 비어있는 요소가 되어버린다.
비어있는 요소undefined를 할당한 요소는 출력 결과부터 달라서 동작이 아예 달라진다.

배열도 '객체'이기 때문에 특정 인덱스에 값을 지정할 때 비로소 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주소값을 저장하는 등의 동작을 한다.
즉 값이 지정되지 않은 인덱스는 아직 존재하지 않은 프로퍼티에 지나지 않고, 이럴 때 나오는 undefined는 아직 존재하지 않는 프로퍼티에 접근했음을 알려주는 것과 같은 것이다.

그래서 undefined의 의미는
1. 사용자가 명시적으로 부여한 경우
2. 비어있는 요소에 접근하려할 때 반환되는 경우

이 두가지로 구분할 수 있다.

1번의 경우 undefined라는 하나의 값으로 동작하기 때문에 순회의 대상도 될 수 있다.
2번의 경우는 해당 프로퍼티 내지 배열의 키값 자체가 존재하지 않음을 의미하기 때문에 문자 그대로 값이 없음을 나타낸다.

** 보통 var a라고 선언만 할 경우 자동으로 undefined가 할당된다고 소개하지만 실제로는 아무것도 할당하지 않고, 이후 변수 a에 접근하고자 할 때 비로소 undefined를 반환하는 것이 맞다고 한다.

의미가 2가지가 되면서 혼란스러워지기 때문에 undefined가 반환되는 상황을 자바스크립트 엔진이 반환하는 경우로만 제한하는 것이 좋다.

null

비어있음을 명시적으로 나타내고 싶을 때에는 undefined 대신 null을 사용하면 된다.
애초에 이런 용도로 만든 타입이라고 한다..ㅎㅎ

한가지 주의할 점은 typeof nullobject라는 것이다.
근데 이게! 자바스크립트 자체 버그라는..?!

그렇게 때문에 null을 걸러야 하는 상황이 있다면 일치 연산자(===)를 사용해서 정확히 판별하는 것이 좋다~

0개의 댓글