자바스크립트의 데이터 타입(data type) & 메모리 공간의 확보와 참조 & TDZ & 형변환

Tori·2024년 11월 8일

JavaScript

목록 보기
1/7
post-thumbnail

자바스크립트 데이터 타입

자바스크립트의 모든 값은 데이터 타입(줄여서 '타입')을 갖고 크게 두 가지로 나눌 수 있다.


원시 타입(Primitive Types)

Number: 숫자 (정수, 실수)
String: 문자열
Boolean: true/false
Undefined: 정의되지 않은 값
Null: 값이 없다는 것을 의도적으로 명시할 때 사용하는 값
Symbol: ES6에서 추가된 고유한 식별자
BigInt: 큰 정수를 다루기 위한 타입



객체 타입

Object: 객체
Array: 배열
Function: 함수
Date, RegExp 등






원시 타입의 데이터타입들 자세히 알아보기

숫자형 타입

  • C, Java의 경우 정수(소수점 이하가 없는 숫자), 실수(소수점 이하가 있는 숫자)를 구분해서 Integer, Long, Float, Double 등과 같은 다양한 숫자 타입을 제공
  • 자바스크립트는 하나의 숫자 타입만 존재한다.
  • ECMAScript 사양에 따르면 숫자타입의 값은 배정밀도 64비트 부동소수점 형식을 따른다.
    모든 수를 실수로 처리하며, 정수만 표현하기 위한 데이터 타입(integer type)이 별도로 존재하지 않는다.
    배정밀도 64비트 부동소수점 형식 (IEEE 754)

숫자타입의 특별한 값

  • Infinity: 양의 무한대
  • -Infinity: 음의 무한대
  • NaN: 산술 연산 불가 (not-a-number)



문자열 타입

  • 문자열은 작은따옴표(''), 큰 따옴표(""), 백틱(` ` )으로 텍스트를 감싼다.

템플릿 리터럴

  • ES6부터 템플릿 리터럴이라는 새로운 문자열 표기법이 도입
  • 멀티라인 문자열(multiline string), 표현식 삽입(expressiont interpolation), 태그드 템플릿(tagged template) 등 편리한 문자열 기능을 제공한다.
  • 템플릿 리터럴은 런타임에 일반 문자열로 변환되어 처리

표현식 삽입

  • 문자열은 문자열 연산자 +를 사용해 연결할 수 있다.
  • +연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작한다. 그 외의 경우는 덧셈 연산자로 동작한다.
var first = 'kim';
var last = 'orange';
// ES5 문자열 연결
console.log('My name is ' + first + ' ' + last + '!');
  • 표현식 삽입을 통해 간단히 문자열을 삽입, 문자열 연산자보다 가독성 좋고 간편하게 문자열 조합할 수 있다.
console.log(`1 + 2 = ${1 + 2}); // 1 + 2 = 3



불리언 타입

  • 불리언타입의 값은 논리적 참, 거짓을 나타내는 true, false뿐이다.
  • 참과 거짓으로 구분되는 조건에 의해 프로그램의 흐름을 제어하는 조건문에서 자주 사용한다.



undefined 타입

  • undefined 타입의 값은 undefined가 유일하다.
  • var 키워드로 선언한 변수는 암묵적으로 undefined로 초기화된다.
  • 변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이뤄질 때 까지 빈 상태(대부분 비어있지 않고 쓰레기 값(garbage value)이 들어있다)로 내버려두지 않고 자바스크립트 엔진이 undefined로 초기화한다. 따라서 변수를 선언한 이후 값을 할당하지 않은 변수를 참조하면 undefined가 반환된다.
  • undefined는 개발자가 의도적으로 할당하기 위한 값이 아닌 자바스크립트 엔진이 변수를 초기화 할 때 사용하는 값이다.



null 타입

  • null 타입의 값은 null이 유일하다.
  • 프로그래밍 언어에서 null은 변수에 값이 없다는 것을 의도적으로 명시(의도적 부재 intentional adsence)할 때 사용한다.
  • 변수에 null을 할당하는 것은 변수가 이전에 참조하던 값을 더 이상 참조하지 않겠다는 의미다.
    - 이는 이전에 할당되어 있던 값에 대한 참조를 명시적으로 제거하는 것을 의미하며, 자바스크립트 엔진은 누구도 참조하지 않는 메모리 공간에 대해 가비지 콜렉션을 수행한다.



심볼 참고

심볼 타입

  • 심볼(Symbol)은 변경 불가능한 원시 타입의 값이다.
  • 자바스크립트는 객체 프로퍼티 키로 오직 문자형과 심볼형만을 혀용하는데 심볼 값은 다른 값과 중복되지 않는 유일무이한 값으로 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해 사용한다.

Symbol()을 사용해서 심볼값 만들기

// id는 새로운 심볼이 된다.
let id = Symbol();

심볼 설명 붙이기

심볼을 만들 때 심볼 이름이라 불리는 설명을 붙일 수도 있다. 심볼 이름은 디버깅 시 아주 유용하다.

let id = Symbol("id");

유일성이 보장되는 자료형

  • 심볼은 유일성이 보장되는 자료형이기 때문에 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값이 다르다.
  • 심볼에 붙이는 설명(설명 이름)은 어떤 것에도 영향을 주지 않는 이름표 역할만 한다.

설명이 같은 심볼 2개를 만들고 비교 - 동일 연산자(==)로 비교시 false가 반환된다.

let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false

심볼은 문자형으로 자동 형 변환되지 않는다.

자바스크립트에선 문자형으로의 암시적 형 변환이 자유롭게 일어나는편이다. alert함수가 거의 모든 값을 인자로 받을 수 있는 이유가 이 때문이다. 그러나 심볼은 예외이다.
심볼은 다른 자료형으로 암시적 형 변환(자동 형 변환)되지 않는다.

'숨김' 프로퍼티

심볼을 이용하면 숨김(hidden) 프로퍼티(외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티)를 만들 수 있다.

  • 예를 들어서 서드파티 코드에서 가져온 객체가 있을 때 함부로 새로운 프로퍼티를 추가할 수 없다. 그런데 심볼은 서드파티 코드에서 접근할 수가 없기때문에 심볼을 사용하면 서드파티 코드가 모르게 객체에 식별자를 부여할 수 있다.

Symbols in a literal

  • 객체 리터럴 {...}을 사용해 객체를 만든 경우, 대괄호를 사용해 심볼형 키를 만들어야 한다.
let id = Symbol("id");
let user = {
  name: "John",
  [id]: 123 // "id": 123 은 안됨 -> "id": 123라고하면, 심볼 id가 아닌 문자열 id로 키가 된다.
}

심볼은 for...in 에서 배제된다

  • 키가 심볼인 프로퍼티는 for...in 반복문에서 배제된다. (심볼로 직접 접근하면 잘 작동됨)
    - 의도치 않게 프로퍼티가 수정되는 것을 예방
  • Object.keys(obj) 에서도 키가 심볼인 프로퍼티는 배제된다.
    - 심볼형 프로퍼티 숨기기(hiding symbolic property)라 불리는 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못함
  • Object.assing은 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사한다.
    - 의도적으로 설계된 것으로 객체를 복사/병합할 때 심볼을 포함한 프로퍼티 전부를 사용하고 싶어 할 것이라는 생각에서 설계되었다.

전역 심볼

  • 심볼은 이름이 같더라도 모두 별개로 취급되지만 이름이 같은 심볼이 같은 개체를 가리키길 원하는 경우 전역 심볼 레지스트리(global symbol registry)를 이용하면 된다.
  • 전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이른이 같은 경우 항상 동일한 심볼을 반환한다.
  • 레지스트리 안에 있는 심볼을 읽거나 새로운 심볼을 생성하려면 Symbol.for(key)를 사용
    - 이 메서드를 호출하면 이름이 key인 심볼을 반환하고, 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 Symbol(key)를 만들고 레지스트리 안에 저장한다.
  • Symbol.keyFor는 전역 심볼 레지스트리를 뒤져서 해당 심볼의 기름을 얻어낸다.
    - 검색 범위가 전역 심볼 레지스트리기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없음
    • 전역 심볼이 아닌 인자가 넘어오면 Symbol.keyForundefined를 반환





참조 타입의 데이터타입들

Object

Array

Function

RegExp 등



위 내용들과 연관해서 알아야 될 개념들 알아보기



var와 let의 초기화 차이 - TDZ(Temporal Dead Zone)

var와 let은 초기화 과정에서 중요한 차이가 있다.

var

  • var 키워드로 변수를 선언하면 변수 호이스팅에 의해 변수 선언문이 스코프 선두로 끌어 올려진 것처럼 동작
  • 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 "선언 단계"와 "초기화 단계"가 동시에 진행됨
  • 선언 전에도 참조 가능 (할당문 이전에 변수 참조시 Undefined를 반환)

let

  • 선언과 초기화가 분리되어 진행됨
  • 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행 됨
  • 호이스팅은 되지만 초기화되지 않은 상태로 TDZ에 들어감
  • 초기화 단계 실행되기 이전에 변수에 접근할 경우 참조 불가 (ReferenceError 발생)
  • 스코프의 시작 지점부터 초기화 시작 지점(변수 선언문에서 초기화 단계 실행)까지 변수를 참조할 수 없는 구간을 일시적 사각지대(Temporal Dead Zon)라고 부름



데이터 타입에 의한 메모리 공간의 확보와 참조

원시 타입의 메모리 처리: 스택 메모리에 값이 직접 저장

참조 타입의 메모리 처리: 힙 메모리에 값(객체( 저장, 스택에는 그 주소(참조)값 저장

  • 자바스크립트 엔진은 데이터 타입, 값의 종류에 따라 정해진 크기의 메모리 공간을 확보한다.
    - 변수에 할당되는 값의 데이터 타입에 따라 확보해야 할 공간의 크기가 결정된다.
    - ECMAScript 사양은 문자열(2바이트)과 숫자(64비트 부동소수점) 타입 외의 데이터 타입의 크기를 명시적으로 규정하고 있지는 않다.


메모리 구조

메모리 구조 예시

// 힙과 스택의 관계
let person = {
  name: "John",
  age: 30,
  hobbies: ["reading", "music"]
};

스택 메모리							힙 메모리
------------------           	  ------------------------------
|                |                |                            |
|  person: #123  |      ---->     | #123:                      |
|                |                |   name: "John"             |
------------------                |   age: 30                  |
                                  |   hobbies: #456            |
                                  ------------------------------
                                  | #456: ["reading", "misuc"] |
                                  ------------------------------



Number 형변환의 종류와 성능비교

1. 주요 형변환 방법들

// 1. Number() 생성자 함수
let num1 = Number("123"); // 명시적 변환

// 2. parseInt() / parseFload()
let num2 = parseInt("123"); // 정수로 변환
let num3 = parseInt("123.45"); // 부동소수점으로 변환

// 3. 단항 연산자 +
let num4 = +"123"; // 암시적 변환

// 4. Math.floor(), Math.ceil(), Math.round()
let num5 = Math.floor("123.45"); // 내림
let num6 = Math.ceil("123.45"); // 올림
let num7 = Math.round("123.45"); // 반올림

// 5. 비트 연산자 활용(|0, ~~)
let num8 = "123" | 0; // 비트 OR 연산
let num9 = ~~"123"; // 이중 NOT 비트 연산

2. 성능 비교

Performance.now()를 사용하여 성능 비교

// 성능 테스트 함수
function performanceTest(fn, iterations = 1000000) {
    const start = performance.now();
    for (let i = 0; i < iterations; i++) {
        fn();
    }
    return performance.now() - start;
}

// 다양한 방법 테스트
const testValue = "123.45";

const tests = {
    'Number()': () => Number(testValue),
    'parseInt()': () => parseInt(testValue),
    'parseFloat()': () => parseFloat(testValue),
    'unary operator +': () => +testValue,
    'Math.floor()': () => Math.floor(testValue),
    'Bitwise OR': () => testValue | 0,
    'Double NOT': () => ~~testValue
};

// 각 방법의 성능 측정
Object.entries(tests).forEach(([name, fn]) => {
    const time = performanceTest(fn);
    console.log(`${name}: ${time.toFixed(2)}ms`);
});

3. 형변환 방법별 특징과 제한사항

연산 속도면에서 차이가 있다.

  • Number() 생성자 함수: 가장 엄격한 변환
  • parseInt / parseFloat: 문자열 파싱에 더 유연함
  • 단항 연산자 (+): 가장 빠른 방법 중 하나(부동소수점 변환)
  • 비트 연산자(|, ~~): 정수만 처리 가능, 정수 변환 중 가장 빠름

숫자로 형변환시 중요성과 주의점

암시적 형변환은 코드를 간결하게 작성할 수 있는 장점이 있지만, 동시에 버그의 원인이 될 수 있다.

  • if(0 == '') 0과 빈 문자열이 암시적 형 변환을 통해 같다고 평가 된다.

암시적 형 변환의 규칙을 이해하고, 필요한 경우 명시적 형 변환을 사용하여 코드의 명확성을 높이는 것이 중요하다.
- 암시적 형 변환이 예상치 못한 결과를 초래하고, 코드의 가독성을 저하시킬 수 있기 때문



Symbol & iterable 객체

iteration protocol

: ES6에서 도입된 이터레이션 프로토콜은 순회 가능한(iterable) 데이터 컬렉션(자료구조)을 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙

  • iterable은 반복 가능한 객체를 의미하며, Symbol.iterator를 구현한 객체이다.

이터러블 프로토콜(iterable protocol)

  • Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환, 이러한 규약을 이터러블 프로토콜이라 한다.
  • 이터러블 프로토콜을 준수한 객체가 이터러블이다.
  • 이터러블은 for...of문으로 순회할 수 있다.
  • 스프레드 문법
  • 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.

이터레이터 프로토콜(iterator protocol)

  • 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환
  • 이터레이터는 next 메서드를 소유, next 메서드를 호출하면 이터러블을 순회하며 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환, 이러한 규약을 이터레이터 프로토콜이라 한다.
  • 이터레이터 프로토콜을 준수한 객체를 이터레이터라 한다.
profile
🌿

0개의 댓글