[코어 자바스크립트] 데이터 타입

변진상·2024년 2월 26일
1

학습 기록

목록 보기
21/31

코어 자바스크립트를 읽고 스터디 세션에서 공유를 위해 정리한 글입니다.

01. 데이터 타입의 종류

↑ 데이터 타입의 종류

  • 기본형과 참조형의 구분 기준
    • 일반적으로는 할당, 연산 시 기본형은 → 복사, 참조형은 참조
    • 엄밀히 말하자면 기본형과 참조형 모두 복사한다.
      • 기본형: 값이 담긴 메모리 공간의 주소를 복사
      • 참조형: 여러 값들의 주소들로 이루어진 묶음을 가리키는 메모리 공간의 주소를 복사
    • 기본형은 불변성(immutability)을 띈다.
      • let a = 6; a = ‘a’; 와 같이 기본형에서 재할당이 가능하기에 값이 변할 수 있는데 어째서 immutable한지는 이후에 설명하겠다.

02. 데이터 타입에 대한 배경지식

1-2-1 메모리와 데이터

  • 메모리에 수 많은 비트들이 존재하고 이 비트들에 저장된 값들의 위치 파악을 용이하게 하기 위해 식별자 사용
    • 식별자 하나가 하나의 비트를 가리키면 매우 비효율적이다. 그리고 매우 많은 비트를 한 단위 묶어 한 식별자로 구분할 경우 검색 시간을 줄이고 나타낼 수 있는 데이터의 개수도 많아지겠지만 낭비되는 비트가 생길 수 있다. → 이런 고민의 결과로 8bit를 묶은 바이트(byte)라는 단위가 나옴.
    • C언어의 경우 자료형에 따라 다른 메모리 크기를 사용하지만 Javascript의 경우 하드웨어적 발달에 따라 메모리 관리에 대한 압박에서 자유로워짐.(숫자. 정수, 실수형 구분하지 않고 8바이트 사용)
    • 모든 데이터는 바이트 단위의 식별자, 더 정확하게는 메모리 주솟값(memory address)을 통해 서로 구분하고 연결함.

1-2-2 식별자와 변수

  • 변수와 식별자를 혼용하는 경우가 있다.
  • 결론
    • 변수변할 수 있는 수 라는 수학용어 차용. 컴퓨터 용어에서는 변할 수 있는 무언가(variable이라는 형용사를 명사로 확장) 즉, 변할 수 있는 데이터를 의미한다.
    • 식별자는 이런 변수를 식별하는 데 사용하는 이름, 즉 변수명이다.

03. 변수 선언과 데이터 할당

1-3-1 변수 선언

  • 문법이 아닌 동작 원리를 알아보고자 함.
var a;
  • 위 코드를 말로 풀면 변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다. 가 된다.
  • 변수란 결국 변경 가능한 데이터가 담길 수 있는 공간이라는 설명이 적절하다.

↑ 변수 선언에 대한 메모리 영역의 변화(실제 메모리 구조보다는 추상화 시켜 표현)

  • 예제 1-1의 명령을 받은 컴퓨터가 메모리에 비어있는 공간 하나 확보해 1003번 공간으로 정하고 이 공간의 이름(식별자)를 a라고 지정한다. 여기까지가 변수 선언 과정이다.
  • 사용자가 변수에 접근할 경우 a라는 이름을 가진 주소를 검색해 해당 공간에 담긴 데이터를 반환한다.

1-3-2 데이터 할당

var a; // 선언
a = 'abc'; // 할당

var a = 'abc'; // 변수 선언과 할당을 한 문장으로 표현
  • 앞서 언급한 메모리 공간의 비어있는 값 부분에 ‘abc’를 할당할 것 같다.
  • 하지만 JS에서는 다르게 동작한다. ‘abc’를 저장하기 위한 별도의 공간(데이터 영역)을 확보해 ‘abc’를 저장한 후 식별자를 a로 가지는 메모리의 공간(변수 영역)의 값에 ‘abc’를 저장한 공간의 주소를 저장한다.
    • 이 책에서 설명하기 위해 사용하는 변수영역과 데이터 영역은 공식 문서가 없어 이해를 돕기 위해 필자가 만들어낸 명칭이라함.

↑ 데이터 할당에 대한 메모리 영역의 변화

변수 영역과 데이터 영역을 나누는 이유

  • 결론: 데이터 변환을 자유롭게 하고 메모리를 더욱 효율적으로 관리하기 위해

💡 데이터 변환의 경우(변수 재할당)
만약 변수 영역에 해당하는 단계에서 데이터를 직접적으로 저장할 경우 데이터의 길이가 변했을 때, 이전 데이터에 맞춰 확보된 공간을 변환된 데이터 크기에 맞춰 늘리거나 줄이는 작업 필요. → 이는 변환하고자 하는 메모리 공간 뒤의 데이터들을 다 뒤로 밀고 그 밀려난 새로운 주소를 식별자와 매핑해줘야 한다.
↑ 변수영역에 해당하는 영역에서 바로 데이터 변환이 이뤄지는 경우의 비효율성

💡 메모리 효율적 관리의 경우(같은 값을 가지는 여러 변수)
- 한 값을 가지는 여러 변수를 만드는 경우 실질적 데이터 저장을 위한 공간이 변수의 개수 * 데이터 크기 만큼의 공간 필요
- 데이터 영역으로 분리해서 저장 시, 1개의 데이터 크기만큼의 공간만 필요
↑ 데이터 영역을 분리하지 않았을 경우 메모리 공간 차원에서의 비효율성

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

1-4-1 불변값

Q. const 상수는 변경 불가능하니 불변값이 아닌가요?
A. 결론

  • 변수(variable) vs. 상수(const): 변수영역에 해당하는 변수 공간에 다른 데이터를 재할당 불가한가 여부로 구분
  • 불변성(immutability): 데이터 영역에 해당하는 변수 공간에 다른 데이터를 재할당 불가능한가 여부로 구분
  • 기본형 데이터인 Number, Boolean, String, Symbol, undefined, null은 불변값이다.(immutable)
var a = 'abc';
a = a + 'def'; // abc = 'abcdef';

var b = 5;
var c = 5;
b = 7;

  1. 변수 a의 값이 ‘abc’에서 ‘abcdef’로 변경된다고 해도 5004의 ‘abc’ 값이 변하는 것이 아니다.(불변값인 이유)
  2. 5005 데이터 공간을 확보하고 ‘abcdef’ 값을 저장. 변수 공간의 주솟값 갱신
  3. 변수 b, c가 같은 값이 5를 가리키다가 b가 7로 변환 되어도 기존의 데이터 영역의 5007를 바꾸는 것이 아니다.
  4. 5008 데이터 공간을 확보 후 7 저장 후 다시 가리키는 주소를 갱신.

→ 이 경우 사용되지 않는 데이터 공간인 5004 → ‘abc’만 가비지 컬렉터에 의해 제거되고 이 값만이 불변성을 가지지 않는다.

1-4-2 가변값

  • 불변값이었던 기본형 외에는 가변값일 것 같지만 설정에 따라 변경 불가능한 경우가 있고, 아예 불변값으로 사용하는 방안도 있다.(이후에 소개)
  • 기본적으로 참조형 타입들은 가변값이다.
  • 기본형 데이터와의 차이는 객체의 변수(프로퍼티) 영역이 별도로 존재한다.
  • 데이터 갱신 시 데이터 영역에서 새로운 객체를 만드는 것이 아니라 기존의 객체 내부에 값이 바뀌기 때문에 가변값이라고 한다.
var obj1 = {
	a: 1,
	b: 'bbb'
};

obj1.a = 2

↑ 데이터 영역은 공유하나 obj1의 변수영역을 따로 가진다. 만약 obj1에 참조형 데이터가 프로퍼티로 들어오게 된다면 그 데이터만의 변수영역을 따로 가지게된다.

  • @5005의 참조 카운트는 @7002에 저장돼 있던 시점 까지는 1이었다가 0이 된다.
  • 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.

JS의 가비지 컬렉팅 방식
이렇게 참조 카운팅을 이용한 가비지 컬렉팅 알고리즘을 Reference-counting garbage collection 이라고 한다. 하지만 순환 참조의 경우 카운팅이 0이 되지 않기 때문에 지금은 사용하지 않는다고 한다. 지금은 Mark-and-sweep 알고리즘을 사용한다고 한다. 대략적으로 트리 순회하며 방문처리(mark)해 중복된 방문을 막고 연결된 객체들을 순회한 후 도달할 수 없는 (non-reachable) 객체를 메모리에서 해제.

1-4-3 변수 복사 비교

  • 참조형 데이터와 기본형 데이터의 경우 변수 복사에 따른 동작이 다른데 이부분에 대해 비교해본다.
var obj1 = {
	prop1: 1,
	prop2: 'bbb'
}
var obj2 = obj1;

var c = 1;
var d = c;

obj1.prop1 = 2;
// obj2.prop1도 2로 바뀜

d = 3;
// c는 1이고 d는 3으로 바뀜

  • 기본형 데이터의 경우 c, d가 서로 다른 주소를 바라보게 되었지만 참조형 객체의 경우 같은 객체를 바라보고 있기 때문에 prop1의 값이 같다.
  • 대부분의 자바스크립트 책이 설명을 쉽게 하기 위해 기본형은 값을 복사하고 참조형은 주소값을 복사한다고 설명하지만, 엄밀히 따지면 자바스크립트의 모든 데이터 타입은 변수에 할당하기 위해 주소값을 복사해야하기 때문에 참조형이라 할 수 있다.

5. 불변 객체

1-5-1. 불변 객체를 만드는 간단한 방법

  • 불변 객체는 최근 React, Vue.js, Angular 등의 라이브러리나 프레임워크뿐만 아니라 함수형 프로그래밍, 디자인 패턴에서 중요하다.
  • 이를 돕는 도구로는 immutable.js, immer.js, immutability-helper 등의 라이브러리가 있다.
  • ES6에서는 spread operator, Object.assign 메서드 등도 같은 목적으로 사용 가능하다.

1-5-2. 얕은 복사와 깊은 복사

  • 책에서는 객체 복사 → 얕은 복사의 문제 점 → 이를 개선한 깊은 복사 함수 구현에 대한 내용을 담고 있는데, 해당 주제에 대해서는 가볍게 넘어가고 깊은 복사 코드만 소개하겠습니다.

1. 재귀함수를 이용한 깊은 복사

const copyObjectDeep = (target) => {
  let result = {};

  if (typeof target === 'object' && target !== null) {
    for (let prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    result = target;
  }

  return result;
}

const obj1 = {
  a: 1,
  b: 'lion',
  c: {
    a: 1,
    b: 'lion'
  }
}

const obj2 = copyObjectDeep(obj1); 

obj1.a = 'otter'

console.log(obj1); // { a: 'otter', b: 'lion', c: { a: 1, b: 'lion' } }
console.log(obj2); // { a: 1, b: 'lion', c: { a: 1, b: 'lion' } }

2. JSON을 이용한 깊은 복사

  • 객체를 문자열로 바꾼 후 다시 객체화 시키는 방식으로 깊은 복사가 가능하다.
const copyObjectDeepViaJSON = (target) => {
  return JSON.parse(JSON.stringify(target));
}

6. undefined와 null

  • JS에서 없음을 나타내는 두 값, 의미가 미세하게 다르고 사용 목적이 다르다.

undefined

  • 사용자가 명시적으로 부여하는 경우, 자바스크립트가 자동으로 부여하는 경우가 있다.(후자를 살펴본다.)
  • 자바스크립트 엔진은 사용자가 응당 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로는 그렇게 하지 않았을 때 undefined를 반환한다.
    1. 값을 대입하지 않은 변수, 즉 데이터 영역에 메모리 주소를 지정하지 않은 식별자에 접근하려고 할 때

    2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때

    3. return 문이 없거나 호출되지 않는 함수의 실행 결과

      var a;
      console.log(a); // undefined
      
      var obj = { a: 1 };
      console.log(obj.b); // undefined
      
      function func() { }
      const c = func();
      console.log(c);
  • 1번의 경우 배열에서는 다르게 동작한다.
var arr1 = [];
arr1.length = 3;
console.log(arr1); // [empty × 3]

var arr2 = new Array(3);
console.log(arr2); // [empty × 3]

var arr3 = [undefined, undefined, undefined];
console.log(arr3); // [undefined, undefined, undefined]
  • 배열에 empty라는 빈 요소를 확보했지만 undefined 조차 할당되어 있지 않다.
  • 이는 배열 순회 메서드들(foreach, filter, reduce, map…)의 순회 대상에서 할당되지 않은 값을 제외하기 위해서 비어있는 요소를 확보한다.
  • 배열도 객체와 마찬가지로 특정 인덱스(객체에서는 프로퍼티의 식별자)에 값을 지정할 때 배열의 변수 공간에 빈 공간을 확보하고 데이터의 주솟값을 저장하는 동작을한다. 즉, 값이 지정되지 않은 인덱스는 ‘아직 존재하지 않는 프로퍼티’와 같다.

💡 사용자가 직접 할당한 undefined는 실존하는 데이터이다!

  • 사용자 할당 undefined는 실존하는 데이터
  • JS 엔진이 반환해주는 undefined는 문자 그대로 값이 없음을 의미한다.
    → 혼란을 유발할 수 있기 때문에, 직접 undefined를 할당하지 않고 JS 엔진이 반환하는 경우 즉, 우리의 통제 범위를 벗어난 경우에만 undefined가 할당될 수 있도록 하는 것으로 혼란을 피할 수 있다.

💡 그럼 비어있음을 명시적으로 나타내기 위해서는 어떻게 할 것인가?

  • null을 사용하면 되고 애초에 이런 용도로 만든 데이터 타입이다.
  • 이런 규칙을 따르면 undefined가 값을 대입하지 않은 변수에 접근할 때 JS엔진이 반환하는 값으로만 존재할 수 있다.

type of null은 object이다.

  • null 체크는 ===(일치 연산자)로 판별하자
  • ==(동등 연산자)를 이용하면 undefined == null은 true이다.

7. 정리

  • 변수: 변경 가능한 데이터가 담길 수 있는 공간

  • 식별자: 그 변수의 이름

  • 변수 선언 → 메모리의 빈 공간(변수 영역)에 식별자 저장 → 자동으로 undefined 할당(var의 경우)

    • 기본형 데이터 할당 → 별도의 공간(데이터 영역)에 데이터를 저장하고 그 공간의 주소를 변수의 값 영역에 할당
    • 참조형 데이터 할당 → 내부 프로퍼티를 저장하기 위한 또 다른 변수 영역을 만들고 확보된 주소를 변수에 연결하고, 앞서 확보한 변수영역에 각 프로퍼티의 식별자 저장 후 데이터를 별도의 공간(데이터영역)에 저장 후 그 주소를 식별자들과 매칭한다.
  • 참조형 데이터를 불변값으로 사용하는 방법

    • 내부 프로퍼티들을 일일이 복사(깊은 복사)
    • 라이브러리 사용
  • 없음을 나타내는 값

    • undefined: 어떤 변수에 값이 존재하지 않을 경우
    • null: 사용자가 명시적으로 ‘없음을 표현’
profile
자신을 개발하는 개발자!

0개의 댓글