Chapter 6 데이터 타입
in Frontend / Js / Javascript
Data Types
Data Types
- 원시 타입 primitive type (총 8개, … symbol, bigint)
객체 타입 object type (나머지)
- Number Type: 64bit 부동소수점 형식
- special:
NaN
,Infinity
,-Infinity
- 부동소수점도 2진수로 저장
- special:
- String Type: UTF-16bi 유티코드, 전세계 대부분 문자열 표현 가능
- 따옴표 쓰는 이유: 식별자, 키워드와 구분 위함. space allowed
- C, Java 와 다르게 문자열은 불변성을 유지한다
- Template Literal: 런타임에 문자열로 변환
undefined
(선언만 된 변수),null
(의도적 부재)
✨ (용어정리) 원래 선언 (declaration) 과 정의 (definition) 는 다른 process 이다 (like C)
- 선언: 컴파일러에게 식별자를 알리는 것
- 정의: 변수 생성되면 식별자 <— link —> 메모리 주소
하지만 JS는 동시에 되기 때문에 딱히 구분을 짓지 않는다 (다만, 선언은 변수에, 정의는 함수에 쓰임)
Why we need types
선언 시 메모리 공간을 얼마나 확보해야 하는가
- 예: 숫자는 64bit 부동소수점 형식, 8byte 확보한다고 ECMAScript 에서 정의한다
- 참조 시 메모리 주소로부터 얼만큼 참조할 것인가
- 참조된 값을 어떻게 해석할 것인가
A
in binary:01000001
01000001
to number:65
- => is
01000001
a number or a string? - => SYMBOL TABLE에 저장되어 있음 (식별자를 key로 binding해 메모리 주소와 데이터 type, 스코프 관리 등을 담당함)
⁉️ 자바스크립트 엔진이 모든 숫자에 대해 8바이트를 할당한다면, 아주 작은 숫자의 경우 메모리 낭비가 아닐까? 예를 들어, 어떤 숫자들은 1바이트로도 충분할 것 같은데
(GPT답변)
```
- 낭비처럼 보일 수 있다. 예를 들어 숫자 5는 이론적으로 1바이트로도 충분하다.
- 하지만 자바스크립트가 모든 숫자에 대해 64비트 부동소수점 형식(IEEE 754)을 사용하는 데는 중요한 이유들이 있다.
- 단순성: 단일 숫자 형식을 사용하면 엔진 구현이 더 단순하고 빨라진다
- 일관성: 개발자들이 여러 숫자 타입(C/Java의 int, float, double 등)을 고려할 필요가 없다
- 범위: 매우 작은 수부터 큰 수까지 표현 가능하다 (±5.0 × 10^−324 부터 ±1.8 × 10^308 까지)
- 성능: 현대 CPU들은 64비트 연산에 최적화되어 있다
- 더 나아가 현대 자바스크립트 엔진들(V8 같은)은 실제로 다양한 최적화 기법을 사용한다.
- 작은 정수 최적화: V8은 -2^31에서 2^31-1 사이의 정수를 내부적으로 더 메모리 효율적인 방식으로 저장하는 "small integer"(SMI) 표현 방식을 사용합니다
- 히든 클래스: 객체의 경우 다양한 메모리 최적화 기법을 사용합니다
- JIT (Just-In-Time) 컴파일: 자바스크립트 코드를 고도로 최적화된 기계어 코드로 변환합니다
따라서 얼핏 보기에 낭비처럼 보일 수 있지만, 단일하고 일관된 숫자 타입을 사용함으로써 얻는 이점이 메모리 오버헤드보다 더 큽니다. 특히 메모리가 상대적으로 풍부한 현대 하드웨어에서는 더욱 그렇죠.
만약 메모리 사용이 매우 중요한 상황이라면, 다음과 같은 방법을 고려해볼 수 있습니다:
- 이진 데이터 작업을 위한 TypedArrays (Uint8Array, Int32Array 등) 사용
- 성능이 중요한 계산을 위한 WebAssembly 사용
- 매우 큰 정수를 위한 BigInt 사용 (실제로는 더 많은 메모리를 사용함)
```
⁉️ any같이 모르는 타입에 대해서는 얼만큼 불러오고 해석하는가?
JavaScript 엔진은 모든 값의 타입 정보를 항상 추적합니다:
1. 모든 값은 타입 태그(type tag)를 가집니다
- 값이 메모리에 저장될 때 타입 정보도 함께 저장됩니다
- 대부분의 JS 엔진은 NaN-boxing이나 pointer-tagging 같은 기법으로 이를 구현
- V8 엔진의 경우 각 값에 대해:
- SMI (Small Integer): 31비트 + 1비트 태그
- HeapObject: 포인터 + 태그 비트들
- 이를 통해 값을 읽을 때 정확한 타입과 크기를 알 수 있음
2. 런타임에 타입을 모르는 경우는 없습니다
- TypeScript의 'any'와 달리, JS 엔진은 항상 값의 실제 타입을 알고 있음
- 동적 타입 언어라는 것은 변수가 다른 타입의 값을 가질 수 있다는 의미이지,
타입 정보가 없다는 의미가 아님
3. 가비지 컬렉션을 위해서도 필수
- GC가 메모리를 회수할 때 각 값의 정확한 크기와 타입을 알아야 함
- 이를 위해 모든 값은 타입 정보를 포함한 메타데이터를 가짐
⁉️ Symbol Table은 어디에 있지? 실행컨텍스트에서도 본적 없는 내용임.. 전역으로 관리하진 않을테니..?
Symbol Table은 JavaScript 엔진의 컴파일러/인터프리터 부분에서 관리됩니다:
렉시컬 환경(Lexical Environment)의 일부
- 각 실행 컨텍스트는 자신만의 렉시컬 환경을 가지며, 이 안에 심볼 테이블이 포함됨
- 변수, 함수 등의 식별자와 그들의 바인딩 정보를 저장
스코프 체인에 따른 계층 구조
- 전역 스코프부터 지역 스코프까지 각각 독립적인 심볼 테이블 보유
- 중첩된 스코프에서 식별자 검색 시 체인을 따라 상위로 탐색
컴파일 타임과 런타임
- 초기 심볼 테이블은 파싱/컴파일 단계에서 생성
- 실행 중에도 동적으로 업데이트 (예: 새로운 변수 선언)
느낀점
- JS 엔전에 대해 모르니 그러려니 하고 넘어가는게 최선인것 같다
- Typescript 공부 진짜진짜 필요..
Dynamic Typing
정적 타이핑 | 동적 타이핑 |
---|---|
명시적 타입 선언 | 묵시적 타입 검사 |
변수 선언 시점에 타입 검사 | 변수 할당 시점에 타입 검사 |
at Compile Time | at Runtime |
Compiler checks type | Interpreter checks type |
에러 발생 시 컴파일 실패 (에러 발생률 낮음) | 에러 발생 시 런타임 실패 (에러 발생률 높음) |
type 변경 불가능 | type 변경 가능 |
- 변수는 타입을 가지지 않고(not typed),
값
이 타입을 가진다(typed)- 즉, 하나의 변수에 다양한 타입의 값을 자유롭게 할당할 수 있다
- 이것이 JavaScript의 동적 타이핑의 핵심 특징이다
- high_flexiblity, low_reliability
- 그래서 변수 선언 최대한 자제 (사용 시 스코핑, 직관성 잘 유지하기)
⁉️ TS에서 추론이 되는 타입에도 타입명시를 해야할까?
일반적으로 TypeScript에서 타입이 명확하게 추론되는 경우에는 명시적 타입 선언을 하지 않는 것이 좋습니다:
타입 추론이 명확한 경우
// 👎 불필요한 타입 명시 const name: string = "HYP"; // 👍 타입 추론 활용 const name = "HYP";
React useState의 경우
// 👎 타입이 명확한 경우 불필요 const [name, setName] = useState<string>("HYP"); // 👍 초기값으로부터 타입 추론 const [name, setName] = useState("HYP");
타입 명시가 필요한 경우
- 유니온 타입이 필요할 때
- null이 가능한 상태를 표현할 때
- 초기값이 null이나 undefined일 때
// 👍 타입 명시가 필요한 경우 const [user, setUser] = useState<User | null>(null); const [count, setCount] = useState<number | undefined>();
타입스크립트의 타입 추론을 신뢰하고 활용하는 것이 코드를 더 간결하게 만들고 유지보수성을 높입니다.