원래 JS는 재사용 컴포넌트를 위해 함수와 프로토타입 기반 상속을 사용
ES6에서 클래스 문법이 적용됨
extends 키워드로 상속 구현
자식 클래스를 subclasses, 부모 클래스를 superclasses라고 함.
파생 클래스에서 생성자 함수는 기초 클래스의 생성자를 실행할 super()를 호출해야 함.
super()는 부모의 생성자 함수(constructor)를 호출함.
class Person {
constructor(name: string) {
console.log(`${name} is a person.`);
}
}
class Employee extends Person {
constructor(name: string, title: string) {
super(name); // 부모 클래스인 Person의 생성자 호출
console.log(`${name} is an employee and works as a ${title}.`);
}
}
const newEmployee = new Employee("John", "Developer");
생성자 함수 contrcutor는 클래스 내에 정의되며, 새로운 인스턴스가 생성될 때 자동으로 호출
인스턴스 변수의 초기값을 설정, 또는 객체 생성 시 필요한 준비 작업 수행
같은 이름의 메서드라면 자식 클래스로 덮어 씌어짐.
해당 멤버에 자유롭게 접근
외부에서 해당 멤버에 접근 불가, 내부에서만 접근
내부 및 하위 클래스에서 접근 가능
호환된다고 판단되는 두 개의 타입 중 한 쪽에서 private멤버를 가지고 있다면, 다른 한 쪽도 무조건 동일한 선언에 private 멤버를 가지고 있어야 함. protected도 마찬가지.
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // 오류: 'Animal'과 'Employee'은 호환될 수 없음.
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 오류! name은 읽기전용 입니다.
클래스의 생성자에게 멤버 변수를 선언하고 초기화하는 작업을 한 줄로 간결하게 처리
생성자의 매개변수에 접근 제어자나 readonly를 붙여서 사용
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) { // 이 부분 주목
}
}
// 매개변수 프로퍼티가 없으면 이렇게
class Octopus {
readonly name: string; // 여기 선언하고,
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName; // 여기서 할당해야 함.
}
}
각 객체 멤버 접근 방법 제어
const fullNameMaxLength = 10;
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (newName && newName.length > fullNameMaxLength) {
throw new Error("fullName has a max length of " + fullNameMaxLength);
}
this._fullName = newName;
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
클래스 내에 static으로 선언하면
인스턴스 없이 객체 자체의 멤버를 접근할 수 있음.
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
this로 접근하지 않는다.
this는 인스턴스 생성 후 해당 인스턴스에 바인딩 됨.
전역 프로퍼티는 인스턴스마다 독립적으로 유지될 필요가 없는 데이터 처리할 때 사용. 메모리 사용을 최적화함.
다른 클래스들이 파생될 수 있는 기초 클래스.
단, 직접 인스턴스화할 수 없음.
추상 메서드도 있다.
추상 클래스는 일반 메서드와 추상 메서드를 모두 포함할 수 있음.
추상 클래스 상속 시 하위 클래스는 추상 클래스의 일반 메서드는 상속되고,
추상 메서드는 직접 구현을 해야 함.
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("roaming the earth...");
}
}
클래스의 인스턴스 new를 하면 호출되는 함수(constructor)
new를 호출하고 생성자 함수를 실행하면 인스턴스를 얻는다.
클래스를 선언하면 인스턴스 타입(속성, 메서드)과 생성자 함수를 생성한다.
클래스는 타입을 생성하므로, 인터페이스를 사용할 수 있는 동일 위치에서 사용할 수 있음.
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
인터페이스가 올 자리에 클래스가 올 수 있다!
그냥 이것도 가능
class Point {
x: number;
y: number;
}
let point: Point = { x: 1, y: 2 };
객체의 실제 타입보다 객체가 어떤 속성이나 메서드를 가지고 있는지가 중요
클래스의 인스턴스가 인퍼테이스에서 정의한 모든 속성과 메서드를 충족한다면,
해당 클래스의 인스턴스는 인터페이스 타입의 변수에 할당될 수 있음.
class Point {
x: number;
y: number;
}
interface Point {
x: number;
y: number;
}
let point: Point = { x: 1, y: 2 };
추상 클래스도 구현을 강제하지만, 클래스의 성격을 가지고 있음.
(일반 메서드 상속 가능, 단일 상속, 런타임 시 존재 등)
가능함.
타입스크립트에서 클래스는 타입 체크 기능도 있기 때문에 그렇게 쓸 수도 있음.
그리고 생성자 함수가 없으면 아무 것도 없는 생성자 함수가 있는 걸로 간주됨.
클래스에 명시적인 생성자가 없어도 기본 생성자가 사용되지만,
생성자를 명시적으로 선언하여 예상치 못한 동작 방지하고 클래스 의도 명확히 표현하는 게 좋음.