Skip to content

Latest commit

 

History

History
143 lines (108 loc) · 9.78 KB

File metadata and controls

143 lines (108 loc) · 9.78 KB

자바스크립트는 7개의 데이터 타입 존재

구분 데이터 타입 설명
원시 타입 숫자 타입 숫자, 정수와 실수 구분 없이 하나의 숫자 타입만 존재
문자열 타입 문자열
불리언 타입 논리적 참과 거짓
undefined 타입 var 키워드로 선언된 변수에 암묵적으로 할당되는 값
null 타입 값이 없다는 것을 의도적으로 명시할 때 사용하는 값
심벌 타입 ES6에서 추가된 변경이 불가능한 원시 값
객체 타입 객체,함수,배열 등

원시 타입: 변경 불가능한 값

6.1 숫자 타입

  • 하나의 숫자 타입만 존재
  • 모든 수를 실수로 처리, 정수만 표현하는 데이터 타입 별도로 존재 x => 배정밀도 64비트 부동소수점 형식
  • 특별한 값: Infinity(양의 무한대), -Infinity(음의 무한대), NaN(산술 연산 불가) => 대소문자 구별하므로 NAN, Nan, nan 해석 불가

6.2 문자열 타입

  • 0개 이상의 16비트 유니코드 문자(UTF-16)의 집합
  • 작은따옴표, 큰따옴표, 백틱(``) 사용

6.3 템플릿 리터럴

  • 멀티라인 문자열, 표현식 삽입, 태그드 탬플릿 등 문자열 처리 기능
  • 런타임에 일반 문자열로 변환되어 처리됨 백틱 사용

6.3.1 멀티라인 문자열

  • 문자열 내 줄바꿈 등의 공백을 표현하려면 백슬래시()로 시작하는 이스케이프 시퀀스 사용 필요

6.3.2 표현식 삽입

  • 문자열 연산자 "+"를 사용해 연결 가능. 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작함.
var first = 'Ung-mo'
console.log('My name is ' + first '.' );
  • 표현식 삽입 시 ${} 사용 => 문자열이 아니더라도 타입이 문자열로 강제 변환되어 삽입됨
var first = 'Ung-mo'
console.log(`My name is ${first}.` );

6.4 불리언 타입

6.5 undefined 타입

  • var로 선언된 변수는 undefined로 처리됨.
  • 변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이뤄질 때까지 빈 상태로 남기지 않고 자바스크립트 엔진이 undefined로 초기화함.

6.6 null 타입

  • 변수에 값이 없다는 것을 명시하는 값 참조를 명시적으로 제거한다.

6.7 심벌 타입

  • 변경 불가능한 원시 타입의 값
  • 다른 값과 중복되지 않는 유일무이한 값
  • 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해 사용됨

6.8 객체 타입

6.9 데이터 타입의 필요성

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

6.9.2 데이터 타입에 의한 값의 해석

  • 값을 저장할 때 확보해야하는 메모리 공간의 크기를 결정하기 위해
  • 값을 참조할 때 한번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해
  • 메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해

6.10 동적 타이핑

6.10.1 동적 타입 언어와 정적 타입 언어

동적 타입 언어

  • 변수를 선언할 때 타입을 명시하지 않아도 되고, 실행 중에 변수의 타입이 변경될 수 있습니다. 즉, 런타임(runtime)에 타입이 결정됩니다.

    • 변수에 저장된 값에 따라 타입이 결정됨 • 타입 변환이 자동으로 이루어질 수 있음(암시적 변환, type coercion) • 코드가 유연하지만, 예상치 못한 버그가 발생할 가능성이 높음

    let x = 10;   // 숫자(Number)
    x = "Hello";  // 문자열(String) - 타입 변경 가능
    console.log(typeof x);  // "string"

정적 타입 언어

  • 정적 타입 언어에서는 변수의 타입이 코드 작성 시점(컴파일 타임)에 결정되며, 한 번 선언된 변수의 타입은 변경할 수 없습니다. C, Java, TypeScript, Kotlin, Swift 등이 대표적인 정적 타입 언어입니다.

    • 컴파일 시점에서 타입 체크가 이루어짐 → 런타임 오류 방지 가능 • 타입을 명시적으로 지정해야 함 • 코드 안정성이 높지만, 유연성이 떨어질 수 있음

    let x: number = 10;  // 숫자 타입으로 선언
    x = "Hello";  // ❌ 오류 발생 (Type 'string' is not assignable to type 'number')
    특징 JS TS
    타입 선언 필요 없음 필요
    타입 체크 런타임에 수행
    코드 안전성 낮음((유연하지만, 오류 가능성 있음)) 높음(컴파일러가 미리 오류를 잡음)
    타입 변환 암시적 변환 가능 엄격한 타입 검사가 가능

질문 정리:

  1. JS 엔진에서 Primitive가 객체 메서드를 호출할 수 있는 이유 (Boxing)

JS의 primitive 타입은 원래 프로퍼티나 메서드를 갖지 않지만, 메서드 호출 순간 엔진이 내부적으로 ToObject 연산을 수행해 일시적인 래퍼 객체(String/Number/Boolean 객체)를 생성하고, 해당 객체의 프로토타입 체인에서 메서드를 찾아 실행한 후 즉시 폐기한다. 즉, primitive는 immutable이지만 메서드를 사용할 수 있는 이유는 이 임시 박싱 과정 덕분이며, null/undefined는 이 과정을 지원하지 않아 메서드 접근 시 TypeError가 발생한다.

  1. 배열 length는 실제 메모리 크기가 아니고, sparse array가 느린 이유

JS 배열의 length는 단순히 “가장 큰 인덱스 + 1”을 나타내는 가상 프로퍼티이며, 실제 메모리 구조를 의미하지 않는다. 배열이 중간에 구멍(hole)이 많아 sparse해지면 엔진은 배열을 더 이상 연속적 메모리로 최적화할 수 없어 Dense Array에서 HashMap 기반의 구조(Dict/ElementsKind 변환)로 전환한다. 이로 인해 인덱스 접근이 느려지고 JIT 최적화가 깨지며, 결국 sparse array는 구조적 이유로 성능이 급격히 떨어진다.

  1. Map과 Object의 키 비교 방식이 다른 이유

Object는 모든 키를 문자열 또는 Symbol로 강제 변환하여 저장하는 반면, Map은 어떤 타입이든 원본 그대로를 키로 사용하며 SameValueZero 규칙에 따라 비교한다. 따라서 Object에서는 1과 "1"이 같은 키가 되지만, Map에서는 완전히 다른 키로 취급된다. 즉, Object는 “문자열 기반 프로퍼티 저장소”, Map은 “타입 보존형 해시맵”이라는 점에서 구조적으로 목적이 다르다.

  1. Set에서 객체 중복 여부는 reference equality로 판단

Set은 내부적으로 Map과 동일하게 SameValueZero 비교를 사용하며, 객체의 동등성은 그 내부 값이 아니라 참조(레퍼런스)가 동일한지로 판단한다. 그래서 {a:1} 리터럴을 두 번 추가하면 서로 다른 객체이므로 중복 없이 들어가지만, 동일한 변수로 참조를 공유할 경우에만 하나만 저장된다. 즉, Set의 중복 판단은 값 비교가 아니라 참조 동일성 기반이다.

  1. NaN === NaN이 false인 이유와 올바른 비교 방법

IEEE 754 규격에서는 NaN을 “어떤 값과도 동등하지 않은 값”으로 정의하기 때문에 === 비교에서도 항상 false가 된다. NaN을 판별하려면 강제 변환을 하지 않는 Number.isNaN 또는 정확히 NaN만을 true로 판별하는 Object.is(x, NaN)을 사용하는 것이 정석이다. 이는 JS의 역할이 아니라 IEEE 표준에 의해 강제된 동작이다.

  1. typeof NaN이 number인 이유

NaN은 "Not-a-Number"라는 이름과 달리 Number 타입 내부의 특수한 값 표현이며, IEEE 754 부동소수점에서 특정 비트 패턴으로 정의된 숫자 값이다. 따라서 NaN은 연산 오류나 정의 불가 결과를 나타내는 “특수한 숫자”일 뿐, 타입 자체는 number로 분류된다. 즉, 이름이 오해를 유발할 뿐 NaN은 엄연히 Number 타입이다.

  1. -0과 0이 ===에서는 같지만 Object.is에서는 다른 이유

Strict Equality(===)는 언어적 추상화를 위해 +0과 -0을 동일한 값으로 취급하고, NaN을 서로 다르게 본다. 반면 Object.is는 IEEE 754의 실제 비트 표현을 존중하는 SameValue 규칙을 사용하여 +0과 -0을 구분하고, NaN을 동일하게 평가한다. 결국 ===은 “언어 레벨 비교”, Object.is는 “비트 동일성 비교”라서 두 방식이 다르게 동작한다.

  1. JSON.parse 결과가 항상 순수 데이터이고 Symbol이 직렬화되지 않는 이유

JSON 스펙은 string, number, boolean, null, 배열, 그리고 문자열 키를 가진 일반 객체만 허용하며 함수를 비롯해 심볼, undefined, 클래스 인스턴스 등의 구조는 스펙상 표현할 수 없다. 따라서 JSON.stringify는 심볼 키나 함수, undefined 등을 직렬화하지 않으며, JSON.parse는 언제나 **순수한 데이터 구조(POJO와 primitive)**만 복원한다. 이는 언어가 아니라 JSON 스펙의 제약이다.

  1. 구조 분해 할당에서 default 값이 평가되는 방식

구조 분해에서 default 값은 해당 프로퍼티의 결과가 정확히 undefined일 때만 평가되며, null은 값이 존재하는 것으로 간주되어 기본값이 적용되지 않는다. 또한 기본값 표현식은 lazy evaluation이므로 필요할 때만 실행된다. 이런 방식 때문에 구조 분해는 효율적이며, 특히 계산 비용이 큰 함수 호출을 기본값으로 둬도 성능상 문제가 없다.

  1. 얕은 복사(shallow copy)에서 reference가 공유되는 이유와 해결책

얕은 복사는 객체의 최상위 프로퍼티만 새로 복사하고, 그 값이 객체/배열 같은 reference 타입일 경우 참조 주소만 복사하므로 원본과 사본이 내부 데이터 구조를 공유하게 된다. 이 때문에 중첩된 값을 변경하면 서로 영향을 주게 되며, 이를 피하려면 structuredClone, JSON 직렬화(제약 있음), 재귀 deep copy, 또는 lodash.cloneDeep 등을 사용해 깊은 복사를 수행해야 한다.