본문으로 건너뛰기

private과 #

타입스크립트 목차클래스레슨 42 / 51

목차

클래스 속성을 밖에서 함부로 읽거나 고치지 못하게 막는 방법은 크게 두 가지입니다. 타입스크립트의 private·protected는 타입 검사 때만 접근을 막고 실행 전에는 흔적 없이 지워지는 표기이고, 자바스크립트의 # 비공개 필드는 타입을 지운 실행 때에도 밖에서 접근할 수 없도록 막는 런타임 문법입니다.

이전 레슨에서 만든 StampCard의 도장 개수(stamps)를 밖에서 card.stamps = 999처럼 직접 조작하지 못하게 숨기면서, public·private·protected와 # 필드가 타입 검사 단계와 Node 실행 단계에서 각각 어떻게 다르게 동작하는지 비교해 봅니다.

6단계 중 1단계. 2~4번 줄: public owner, 타입스크립트 전용 private stamps, 자바스크립트 비공개 필드 #pin 세 속성을 나란히 적었습니다.

1public · private · protected 세 가지 접근 범위

타입스크립트는 속성이나 메서드 앞에 접근 범위를 적는 세 가지 키워드를 제공합니다.

  • public(기본값): 아무것도 안 적으면 public입니다. 클래스 안팎 어디서나 읽고 쓸 수 있습니다.
  • private: 선언한 그 클래스 본문 안에서만 쓸 수 있습니다. 자식 클래스나 클래스 밖에서는 막힙니다(TS2341).
  • protected: 선언한 클래스 안과 extends로 물려받은 자식 클래스 안에서만 쓸 수 있습니다. 클래스 밖에서는 막힙니다(TS2445).

먼저 private으로 stamps를 감추고 add와 count 메서드로만 다루는 StampCard를 작성해 봅니다.

class StampCard {
  public owner: string;
  private stamps: number = 0;
  constructor(owner: string) {
    this.owner = owner;
  }
  add(n: number): number {
    this.stamps += n;
    return this.stamps;
  }
  count(): number {
    return this.stamps;
  }
}

클래스 안의 add와 count 메서드는 this.stamps를 자유롭게 읽고 고칩니다.

const card =
  new StampCard("민지");
console.log(card.add(2));
console.log(card.count());
실행 결과
2
2

반면 protected는 extends로 상속받은 자식 클래스까지는 접근을 열어 주고, 인스턴스 바깥에서 직접 읽는 것만 막습니다.

class BaseCard {
  protected stamps: number = 0;
}

class VipCard extends BaseCard {
  bonus(): number {
    return this.stamps + 5;
  }
}

const vip = new VipCard();
console.log(vip.stamps);
검사 결과 오류 1개
main.ts(12,17): error TS2445: Property 'stamps' is protected and only accessible within class 'BaseCard' and its subclasses.

2private과 #의 결정적 차이: 타입 지우기 뒤

그렇다면 private stamps로 선언한 속성을 클래스 밖에서 card.stamps로 읽으면 타입 검사와 실행 때 각각 어떻게 될까요?

먼저 private stamps를 클래스 밖에서 읽는 코드를 검사하고 실행해 봅니다.

class StampCard {
  public owner: string;
  private stamps: number = 0;

  constructor(owner: string) {
    this.owner = owner;
  }
}

const card =
  new StampCard("민지");
console.log(card.stamps);
검사 결과 오류 1개
main.ts(12,18): error TS2341: Property 'stamps' is private and only accessible within class 'StampCard'.
실행 결과
0

타입 검사기는 TS2341 오류를 내지만, Node가 타입을 지우고 실행할 때는 private 키워드가 공백으로 사라져 평범한 stamps = 0 속성이 됩니다. 그래서 실행 결과로 0이 그대로 찍힙니다.

이번에는 자바스크립트 비공개 필드 #stamps를 쓰고 클래스 밖에서 card.#stamps를 읽어 봅니다.

class StampCard {
  owner: string;
  #stamps: number = 0;

  constructor(owner: string) {
    this.owner = owner;
  }
}

const card =
  new StampCard("민지");
console.log(card.#stamps);
검사 결과 오류 1개
main.ts(12,18): error TS18013: Property '#stamps' is not accessible outside class 'StampCard' because it has a private identifier.
실행 결과
main.ts:12console.log(card.#stamps);                ^ SyntaxError: Private field '#stamps' must be declared in an enclosing class

#stamps는 타입을 지운 뒤에도 #stamps 그대로 남아, 타입 검사 때는 TS18013으로 막히고 실행 때에도 자바스크립트 엔진이 SyntaxError로 접근을 원천 차단합니다.

구분private stamps#stamps
누구의 문법인가타입스크립트 전용 표기자바스크립트 표준 문법
타입 지우기 뒤private이 지워져 stamps가 됨#stamps 그대로 남음
밖에서 읽을 때(검사)TS2341 오류TS18013 오류
밖에서 읽을 때(실행)값(0)이 그대로 읽힘SyntaxError로 막힘
Object.keys(card)'stamps'가 목록에 나옴목록에 나오지 않음

예제

실행 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

잔액을 #balance 비공개 필드로 숨기고 deposit 메서드로 입금하는 Account 클래스입니다.

class Account {
  readonly owner: string;
  #balance: number = 0;

  constructor(owner: string) {
    this.owner = owner;
  }

  deposit(n: number): number {
    this.#balance += n;
    return this.#balance;
  }
}
const acc = new Account("민지");
console.log(acc.deposit(5000));
console.log(acc.deposit(3000));

무엇이 차례로 찍힐까요?

실행 결과 보기
실행 결과
5000
8000

#balance는 클래스 안의 deposit 메서드에서는 this.#balance로 읽고 쓸 수 있지만, 클래스 밖에서는 타입 검사(TS18013)와 실행(SyntaxError) 양쪽에서 모두 안전하게 보호됩니다.

자주 틀리는 곳

틀린 생각: private을 붙이면 실행 때나 Object.keys에서도 속성이 숨겨진다.

private은 오직 타입 검사기 눈에만 보이는 표기입니다. 실행 시점에는 일반 속성과 똑같아서 Object.keys나 JSON.stringify를 부르면 private 속성이 밖으로 고스란히 드러납니다.

class Account {
  public id: string;
  private pin: number = 1234;
  #secret: number = 9999;

  constructor(id: string) {
    this.id = id;
  }
}

const acc = new Account("a1");
console.log(Object.keys(acc));
실행 결과
[ 'id', 'pin' ]

맞는 규칙: private pin은 'pin'으로 드러나지만 #secret은 전혀 나오지 않습니다(타입이 실행 때 남는다는 생각). 실행 중에도 밖으로 새어 나가면 안 되는 값은 반드시 # 필드로 선언합니다.

틀린 생각: 부모의 private 속성은 extends로 상속받은 자식 클래스에서 읽을 수 있다.

부모가 private이나 #으로 감춘 속성은 자식 클래스에서도 읽을 수 없습니다.

class BaseCard {
  private stamps: number = 0;
}

class VipCard extends BaseCard {
  read(): number {
    return this.stamps;
  }
}
검사 결과 오류 1개
main.ts(7,17): error TS2341: Property 'stamps' is private and only accessible within class 'BaseCard'.

맞는 규칙: 자식 클래스에서 읽어야 하는 속성은 private 대신 protected로 선언합니다.

확인 문제

1. private stamps: number = 0;⁠으로 선언한 속성은 타입을 지우고 Node로 실행하면 어떻게 되나요?

힌트

2절의 표에서 private stamps가 타입 지우기 뒤에 어떻게 바뀌는지 살펴보세요.

정답

private과 : number가 모두 지워져 일반 속성 stamps = 0;이 됩니다. 따라서 타입 검사 때는 TS2341로 막히지만, 실행 시점에는 밖에서 읽어도 0이 그대로 나오고 Object.keys에도 드러납니다(타입이 실행 때 남는다는 생각).

2. 실행 중에도 밖에서 읽거나 Object.keys로 꺼낼 수 없게 완전히 숨기려면 어떻게 선언해야 하나요?

힌트

자바스크립트 표준 비공개 필드 기호를 떠올려 보세요.

정답

자바스크립트 비공개 필드인 #stamps: number = 0;처럼 이름 앞에 #을 붙여 선언합니다. # 필드는 타입을 지운 뒤에도 그대로 남아 실행 때 밖에서 접근하면 SyntaxError로 막힙니다.

3. extends로 물려받은 자식 클래스 안에서는 읽을 수 있게 하되 클래스 밖에서는 막으려면 어떤 접근 제어자를 쓰나요?

힌트

1절에서 배운 세 가지 키워드(public · private · protected) 가운데 하나입니다.

정답

protected를 씁니다. protected 속성은 선언한 클래스와 자식 클래스 본문 안에서는 this.속성으로 읽을 수 있고, 인스턴스 바깥에서 읽으면 TS2445 오류로 막힙니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절의 private stamps 코드와 #stamps 코드를 각각 main.ts에 넣어 npx tsc와 node main.ts로 돌려 결과를 비교해 보세요. 다음 레슨 interface를 지키는 클래스에서는 클래스 선언 자리에서 모양을 점검하는 implements를 배웁니다. 검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

타입스크립트 목차로