학습 공간 개념 · 판단 · 실험

Frontend Performance Lab

TypeScript

클래스 접근성, static과 런타임 경계

컴파일 타임에만 검사하는 TypeScript 접근 제어자와 런타임까지 보호하는 JavaScript #필드, 그리고 생성자 객체에 속하는 static 멤버의 경계를 구분합니다.

이럴 때 필요해요

타입 검사 시점에만 존재하고 빌드 후에 사라지는 규칙과 실제 런타임에 남는 코드·메모리 구조를 혼동하지 않고 설계할 수 있습니다.

어떻게 동작하나요?

TypeScript 클래스에서 프로퍼티 앞에 private을 붙이면 에디터와 컴파일러는 클래스 바깥에서 그 프로퍼티에 접근하는 코드를 에러로 막아 줍니다. 하지만 코드를 빌드해 나온 JavaScript 결과를 열어 보면 private 키워드는 흔적도 없이 사라지고 일반 프로퍼티로 남아 있어 런타임에는 외부에서 읽거나 고칠 수 있습니다. 만약 컴파일 이후의 실제 브라우저·Node.js 런타임에서도 외부 접근을 문법 차원에서 완전히 차단해야 한다면 아래 예제처럼 JavaScript 표준 문법인 #필드(#value)를 사용해야 합니다.

클래스 안에 선언하는 멤버 중 static 키워드가 붙은 프로퍼티와 메서드는 new로 만든 개별 인스턴스가 아니라 클래스 생성자 객체 자체에 소속됩니다. 그래서 Counter.create()처럼 인스턴스를 만들기 전에도 바로 호출할 수 있는 팩토리 메서드나 공통 상수에 자주 쓰입니다. 다만 변경 가능한 데이터를 static 필드에 넣어 두면 모든 인스턴스와 요청이 그 상태를 공유하게 되므로, 사용자별 데이터나 화면별 상태가 섞이지 않도록 주의해야 합니다.

이처럼 '타입으로만 존재하는가, 런타임 객체로도 남는가'의 차이는 enum에서도 나타납니다. 대부분의 TypeScript 타입 선언은 컴파일 후 사라지지만, 일반 enum은 런타임에 실제 객체를 생성하며 특히 숫자 enum은 값에서 이름으로의 역방향 매핑까지 함께 만듭니다. 따라서 런타임 객체가 굳이 필요 없는 단순한 선택지 집합이라면 번들 크기를 늘리지 않는 문자열 리터럴 유니온('idle' | 'running')이나 as const 객체를 우선 검토하는 것이 실용적입니다.

코드 예제

동작을 설명하는 읽기 전용 예제입니다.

example.ts
class Counter {
  #value = 0;
  static create() { return new Counter(); }
  increment() { return ++this.#value; }
}
const counter = Counter.create();
console.log(counter.increment());
typescript · 읽기 전용7줄

static 메서드인 create()는 Counter 생성자 객체에서 바로 호출하고, #value는 각 인스턴스 내부에 캡슐화되어 런타임에도 클래스 외부에서 직접 접근할 수 없습니다.

자주 헷갈리는 포인트

  • TypeScript의 private 접근 제어자가 런타임의 객체 순회나 외부 스크립트 접근까지 막아 주는 보안 장치는 아닙니다.
  • static 멤버는 개별 인스턴스마다 따로 복제되는 값이 아니라 클래스 생성자 객체 하나에 공유되는 멤버입니다.

내 말로 설명해 보기

컴파일된 이후의 JavaScript 런타임 환경에서도 클래스 내부 필드에 대한 외부 접근을 확실히 차단하려면 어떤 문법을 사용해야 할까요?

함께 읽으면 좋은 개념

관련 면접 질문 3개

public·protected·private와 #필드는 무엇이 다른가요?

TypeScript의 public, protected, private는 컴파일 단계에서 클래스 멤버의 접근 범위를 검사하는 키워드입니다. public은 어디서나, protected는 해당 클래스와 파생 클래스에서, private는 선언된 클래스 내부에서만 접근을 허용합니다. 다만 이들은 컴파일 후 JavaScript 출력에서는 사라지는 타입 수준 제약인 반면, JavaScript 표준인 #필드(#value)는 런타임 엔진 차원에서 외부 접근을 문법 오류로 막는 실제 private 필드입니다.

꼬리질문과 참고자료 →

static 멤버는 인스턴스 멤버와 어떻게 다른가요?

인스턴스 멤버는 new로 생성된 각각의 객체에 속해 개별 인스턴스의 상태를 다루는 반면, static(정적) 멤버는 개별 인스턴스가 아니라 클래스 생성자 객체 자체에 속하는 프로퍼티와 메서드입니다. 따라서 인스턴스를 생성하지 않고도 ClassName.method() 형태로 바로 호출할 수 있으며, 주로 팩토리 메서드(예: User.fromJson())나 클래스 전반에서 공유하는 상수·유틸리티 로직을 정의할 때 사용합니다. 다만 변경 가능한 상태를 static 프로퍼티에 두면 애플리케이션 전체에서 공유되는 전역 상태처럼 동작하므로 주의해야 합니다.

꼬리질문과 참고자료 →

Enum의 장단점과 Literal Union 대안은 무엇인가요?

enum은 관련된 상수 집합에 이름을 붙이는 문법이지만, 컴파일 후 사라지는 일반 타입과 달리 런타임에 실제 JavaScript 객체를 생성합니다. 특히 숫자 enum은 값에서 이름으로의 역방향 매핑까지 만들어 코드 크기가 커지고 의도치 않은 키 순회 이슈가 생길 수 있습니다. 그래서 실무에서는 런타임 오버헤드가 없는 리터럴 유니온('idle' | 'loading')이나, 런타임 상수 객체도 필요할 때는 as const 객체를 대안으로 많이 사용합니다.

꼬리질문과 참고자료 →

공식 참고 자료

← TypeScript