9단원 '클래스와 타입'의 세 레슨에서 여덟 문제를 내고, 앞 단원에서 두 문제를 섞었습니다. 끝의 만들어 보기 둘은 직접 코드를 채우고 짜 보는 문제입니다. 이 단원을 처음 본다면 진단 문항인 문제 1 · 3 · 5 · 6부터 풀어 보세요. 막히는 문항에 적힌 레슨만 골라 읽어도 됩니다.
문제 1
(진단 · 클래스에 타입 붙이기) 다음 코드를 타입 검사하면 어떻게 될까요?
class StampCard {
owner: string;
stamps: number;
constructor(owner: string) {
this.owner = owner;
}
}
- ① 문제없이 통과한다 (
stamps는 저절로0이 된다) - ② 3번 줄
stamps에서 초기값이 없다고 막힌다 —TS2564 - ③ 6번 줄
this.owner = owner에서 막힌다 —TS2339
힌트
3번 줄의 stamps에 기본값이나 constructor 안의 대입이 있는지 살펴보세요.
정답
정답: ②
main.ts(3,3): error TS2564: Property 'stamps' has no initializer and is not definitely assigned in the constructor.클래스 본문 맨 위에 stamps: number;라고 선언만 해 두고 기본값(= 0)도 안 적고 constructor에서도 대입하지 않으면, 실행 때 undefined가 들어가는 일을 막기 위해 TS2564 오류를 냅니다.
다시 볼 레슨: 클래스에 타입 붙이기
문제 2
(클래스에 타입 붙이기) 다음 코드에서 타입 검사가 막히는 줄과 오류 코드는 무엇일까요?
class StampCard {
readonly owner: string;
constructor(owner: string) {
this.owner = owner;
}
}
const card =
new StampCard("민지");
card.owner = "준호";
- ① 5번 줄 —
readonly속성이라constructor안에서도 대입할 수 없다 - ② 10번 줄 —
new StampCard("민지")를 부를 수 없다 - ③ 11번 줄 —
readonly속성을 생성 뒤 밖에서 바꾸려 해TS2540으로 막힌다
힌트
readonly 속성에 값을 넣을 수 있는 자리가 어디까지인지 떠올려 보세요.
정답
정답: ③
main.ts(11,6): error TS2540: Cannot assign to 'owner' because it is a read-only property.readonly 속성은 선언 줄이나 constructor 안에서는 초기값을 넣을 수 있지만, 인스턴스가 만들어진 뒤 바꾸려 하면 TS2540으로 막힙니다.
다시 볼 레슨: 클래스에 타입 붙이기
문제 3
(진단 · private과 #) 다음 코드를 타입 검사할 때와 Node로 바로 실행할 때의 결과로 맞는 것은?
class StampCard {
private stamps: number = 0;
}
const card = new StampCard();
console.log(card.stamps);
- ① 타입 검사 때는
TS2341로 막히지만, 실행하면private이 지워져0이 찍힌다 - ② 타입 검사 때도 막히고 실행 때도
SyntaxError가 난다 - ③ 타입 검사 때는 통과하고 실행 때
undefined가 찍힌다
힌트
2번 줄의 private이 타입 지우기 단계 뒤에 남는지 사라지는지 생각해 보세요.
정답
정답: ①
main.ts(6,18): error TS2341: Property 'stamps' is private and only accessible within class 'StampCard'.0private은 타입스크립트 전용 표기라 타입 검사 때는 TS2341로 막아주지만, 실행 전 타입 지우기 단계에서 공백으로 사라집니다(타입이 실행 때 남는다는 생각).
다시 볼 레슨: private과 #
문제 4
(private과 #) 다음 코드를 실행하면 무엇이 출력될까요?
class Account {
public id: string = "a1";
private pin: number = 1234;
#secret: number = 9999;
}
const acc = new Account();
console.log(Object.keys(acc));
- ①
[ 'id' ] - ②
[ 'id', 'pin' ] - ③
[ 'id', 'pin', '#secret' ]
힌트
타입 지우기 뒤에 일반 속성으로 남는 것과 자바스크립트 비공개 필드로 숨겨지는 것을 구분해 보세요.
정답
정답: ②
[ 'id', 'pin' ]private pin은 타입 지우기 뒤 일반 속성 pin = 1234가 되어 Object.keys(acc)에 그대로 나오지만, 자바스크립트 비공개 필드 #secret은 밖으로 드러나지 않습니다.
다시 볼 레슨: private과 #
문제 5
(진단 · interface를 지키는 클래스) 다음 코드를 타입 검사하면 어느 줄에서 왜 막힐까요?
interface Printable {
owner: string;
format(): string;
}
class StampCard
implements Printable
{
owner: string = "민지";
}
- ① 아직
new StampCard()를 부르지 않았으므로 오류가 나지 않는다 - ② 6번 줄
class StampCard에서format메서드가 빠졌다고 막힌다 —TS2420 - ③ 2번 줄
interface Printable에서 막힌다
힌트
Printable 인터페이스가 요구하는 두 항목을 StampCard가 모두 갖추었는지 비교해 보세요.
정답
정답: ②
main.ts(6,7): error TS2420: Class 'StampCard' incorrectly implements interface 'Printable'. Property 'format' is missing in type 'StampCard' but required in type 'Printable'.implements Printable을 적어 두면 인스턴스를 만들기 전이라도 클래스 선언 자리에서 빠진 속성이나 메서드를 바로 잡아 줍니다.
다시 볼 레슨: interface를 지키는 클래스
문제 6
(진단 · interface를 지키는 클래스) 다음 코드를 타입 검사하면 어떻게 될까요?
interface Adder {
add(n: number): number;
}
class Counter
implements Adder
{
count: number = 0;
add(n): number {
this.count += n;
return this.count;
}
}
- ①
Adder에n: number가 적혀 있으므로n이number로 추론되어 통과한다 - ② 9번 줄
add(n)에서 매개변수n의 타입을 안 적어TS7006으로 막힌다 - ③ 5번 줄
class Counter에서count속성이 남는다고 막힌다
힌트
9번 줄의 매개변수 n에 타입 표기가 있는지, 그리고 implements가 매개변수 타입을 추론해 주는지 생각해 보세요.
정답
정답: ②
main.ts(9,7): error TS7006: Parameter 'n' implicitly has an 'any' type.implements는 클래스가 인터페이스의 모양에 맞는지 대조만 할 뿐, 메서드 매개변수 타입을 추론해 채워 주지 않습니다(implements가 매개변수 타입을 추론한다는 생각). add(n: number)처럼 직접 적어야 합니다.
다시 볼 레슨: interface를 지키는 클래스
문제 7
(interface를 지키는 클래스) 다음 코드는 타입 검사를 통과할까요?
class StampCard {
owner: string;
stamps: number = 0;
constructor(owner: string) {
this.owner = owner;
}
}
function show(c: StampCard) {
console.log(c.owner);
}
show({
owner: "민지",
stamps: 3,
});
- ① 통과하고
민지를 출력한다 (모양이 같으면new StampCard가 아니어도 들어간다) - ②
new StampCard(...)로 만든 인스턴스가 아니라서 막힌다 - ③
implements StampCard를 안 적어서 막힌다
힌트
타입스크립트가 이름표를 보는지 모양을 보는지(검사 규칙 4 — 구조적 타이핑) 떠올려 보세요.
정답
정답: ①
민지타입스크립트는 구조적 타이핑(검사 규칙 4)을 따르므로, 공개 속성 모양(owner: string, stamps: number)만 맞으면 일반 객체도 StampCard 자리에 들어갑니다(이름이 같아야만 들어간다는 생각).
다시 볼 레슨: interface를 지키는 클래스
문제 8
(private과 #) 부모 클래스 BaseCard의 속성을 extends로 상속받은 자식 클래스 VipCard 안에서는 읽을 수 있게 하고, 클래스 바깥에서는 읽지 못하게 막으려면 어떤 키워드를 써야 할까요?
- ①
public - ②
protected - ③
private
힌트
자식 클래스까지 접근을 허락하는 접근 제어자를 고르세요.
정답
정답: ②
protected로 선언한 속성은 선언한 클래스 본문과 extends로 물려받은 자식 클래스 본문 안에서 접근할 수 있고, 인스턴스 바깥에서 읽으면 TS2445로 막힙니다.
다시 볼 레슨: private과 #
문제 9
(복습 · 선택 속성과 readonly) 다음 코드에서 타입 검사가 막히는 줄은 어디일까요?
interface Box {
readonly tags: string[];
}
const b: Box = {
tags: ["신상"],
};
b.tags.push("할인");
b.tags = [];
- ① 8번 줄
b.tags.push("할인")만 막힌다 - ② 9번 줄
b.tags = []만 막힌다 (readonly는 속성 줄 재대입만 막고 배열 안 수정은 막지 않는다) - ③ 8번 줄과 9번 줄 둘 다 막힌다
힌트
속성 이름 앞의 readonly가 막는 것과 배열 타입 앞의 readonly가 막는 것을 구분해 보세요.
정답
정답: ②
main.ts(9,3): error TS2540: Cannot assign to 'tags' because it is a read-only property.속성 앞의 readonly는 b.tags = []처럼 속성에 새 배열을 대입하는 것만 막습니다(readonly가 안쪽까지 얼린다는 생각). 배열 안의 요소를 고치는 push까지 막으려면 타입을 readonly string[]으로 적어야 합니다.
다시 볼 레슨: 선택 속성과 readonly
문제 10
(복습 · as는 검사기에게 하는 말) 다음 코드를 실행하면 무엇이 출력될까요?
const raw: unknown = "500";
const n = raw as number;
console.log(n + 100);
- ①
600(as number가 문자열"500"을 숫자500으로 바꾼다) - ②
"500100"(as number는 실행 전에 지워져 문자열 이어 붙이기가 된다) - ③ 타입 검사 오류가 난다
힌트
as number가 실행 때 값을 바꾸는지, 아니면 실행 전에 지워지는지 생각해 보세요.
정답
정답: ②
500100타입 단언 as number는 검사기 눈에만 숫자라고 우길 뿐 실행 전 타입 지우기 단계에서 사라집니다. 따라서 실행 때는 원래 값 "500"에 100이 이어 붙어 "500100"이 출력됩니다.
다시 볼 레슨: as는 검사기에게 하는 말
문제 11 (만들어 보기)
(클래스에 타입 붙이기 · private과 #) 손님 이름을 담는 읽기 전용 속성 readonly owner: string과 실행 때까지 밖에서 읽을 수 없는 비공개 잔액 필드 #cash: number = 0, 그리고 금액 n: number를 받아 #cash에 더하고 새 잔액을 돌려주는 put(n: number): number 메서드를 가진 Wallet 클래스를 작성해 보세요.
(가) 뼈대 채우기 — ___ 자리에 알맞은 선언과 타입을 채워 보세요.
// class Wallet {
// ___ owner: string;
// ___: number = 0;
// constructor(owner: ___) {
// this.owner = owner;
// }
// put(n: ___): ___ {
// this.#cash += n;
// return this.#cash;
// }
// }
(나) 백지에서 짜기 — new Wallet("민지")로 만든 인스턴스 w에 w.put(1000)과 w.put(500)을 차례로 호출한 결과를 출력해 보세요.
힌트
첫 빈칸은 읽기 전용 키워드 readonly이고, 둘째 빈칸은 #으로 시작하는 비공개 필드 이름 #cash입니다.
정답
class Wallet {
readonly owner: string;
#cash: number = 0;
constructor(name: string) {
this.owner = name;
}
put(n: number): number {
this.#cash += n;
return this.#cash;
}
}
const w = new Wallet("민지");
console.log(w.put(1000));
console.log(w.put(500));1000
1500readonly owner: string은 생성 뒤 바꿀 수 없도록 막고, #cash는 타입 검사(TS18013)와 실행(SyntaxError) 양쪽에서 모두 잔액을 보호합니다.
다시 볼 레슨: 클래스에 타입 붙이기 · private과 #
문제 12 (만들어 보기)
(interface를 지키는 클래스) label(): string 메서드를 요구하는 interface Named를 선언하고, implements Named를 붙여 name: string 속성과 label(): string 메서드("[이름]" 반환)를 갖춘 Tag 클래스를 작성해 보세요.
(가) 뼈대 채우기 — ___ 자리에 알맞은 키워드와 타입을 채워 보세요.
// interface Named {
// label(): ___;
// }
// class Tag ___ Named {
// name: ___;
// constructor(name: ___) {
// this.name = name;
// }
// label(): ___ {
// return `[${this.name}]`;
// }
// }
(나) 백지에서 짜기 — new Tag("신상")으로 만든 인스턴스 t의 t.label()을 출력해 보세요.
힌트
클래스가 인터페이스를 지킨다고 선언할 때 쓰는 키워드는 implements입니다.
정답
interface Named {
label(): string;
}
class Tag implements Named {
name: string;
constructor(name: string) {
this.name = name;
}
label(): string {
return `[${this.name}]`;
}
}
const t = new Tag("신상");
console.log(t.label());[신상]class Tag implements Named로 선언해 두면, 만약 label() 메서드를 빠뜨리거나 반환 타입이 string이 아닐 때 class Tag 줄에서 바로 TS2420 오류를 잡아 줍니다.
다시 볼 레슨: interface를 지키는 클래스