一.클래스 멤버
TypeScript 의 클래스 정의는 [ES6 Class 사양](/articles/class-es6 笔记 10/) 과 일치하며, 정적 프로퍼티, 인스턴스 프로퍼티, 액세스 등을 모두 지원합니다:
class Grid {
static origin = {x: 0, y: 0};
}
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
하지만 2 가지 점에 주의가 필요합니다:
-
ES3 는 getter/setter 를 서포트하지 않으므로, ES5+ 로 컴파일 설정해야 합니다
-
getter 만으로 setter 가 없는 프로퍼티는 자동으로
readonly로 추론됩니다 (멤버 수식자 중 하나)
二.멤버 수식자
액세스 제어 수식자
3 개의 액세스 제어 수식자를 서포트합니다:
-
public: 클래스의 멤버 속성/메소드는 기본적으로 모두
public이며, 액세스 제한이 없습니다 -
private: 해당 클래스 선언의 외부에서 그 멤버에 액세스할 수 없습니다 (
this.xxx로 프라이빗 멤버에 액세스할 수 없는 등) -
protected:
private과 유사하지만, 파생 클래스에서도 보호된 멤버에 액세스할 수 있습니다
예를 들어:
class Animal {
// 프라이빗 멤버 속성
private name: string;
// 보호된 멤버 속성
protected ancestor: string;
// 쓰지 않으면, 기본적으로 public
constructor(theName: string) { this.name = theName; }
// public 멤버 메소드
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
주의, 이러한 액세스 제어는 모두 컴파일 시의 제한이며, 런타임에서는 강제적인 체크를 수행하지 않습니다. TypeScript 의 설계 원칙에 부합합니다:
컴파일 산출물에 런타임 오버헤드를 추가하지 않음
또한, 클래스 멤버의 액세스 가능성도 타입 체크의 일부이며, private/protected 수식자는덕 타입을 깨뜨립니다. 예를 들어:
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
}
class Person {
name: string;
constructor(theName: string) { this.name = theName; }
}
// 정확함: 덕 타입 (닮았으면)
let person: Animal = new Person('Sam');
class Plant {
// 프라이빗 멤버 name
private name: string;
constructor(theName: string) { this.name = theName; }
}
// 오류: name 속성의 액세스 가능성이 일치하지 않음 (닮아도 안 됨, 액세스 가능성이 다름)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');
P.S.특별히, protected constructor 는 해당 클래스가 직접 인스턴스화할 수 없음을 나타내지만, 상속은 서포트합니다
readonly 수식자
readonly 수식자를 통해 속성을 읽기 전용으로 선언할 수 있으며, 선언 시 또는 생성자 내에서만赋值할 수 있습니다. 예를 들어:
class Octopus {
readonly name: string;
public 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"; // error! name is readonly.
P.S.물론, readonly 와 액세스 제어 수식자는 모순되지 않으며, 같은 속성에 작용할 수 있습니다
파라미터 속성
생성자 내에서 초기화되는 속성에 대해:
class Octopus {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
간략화 방식 이 있습니다:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
그 중에서, name 은파라미터 속성이라고 불리며, 생성자의 형식 파라미터 이름 앞에 private/protected/public/readonly 수식자를 붙여 선언합니다
三.상속
class A extends B {
//...
}
Babel 변환과 유사합니다 (명확히 하기 위해, 중요한 부분만 발췌):
function _inherits(subClass, superClass) {
// 부모 클래스의 프로토타입 속성을 상속
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// 부모 클래스의 정적 속성을 상속
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
// 서브 클래스 생성자 내에서 부모 클래스의 인스턴스 속성을 상속
function A() {
// 부모 클래스 생성자를 통해 서브 클래스 인스턴스 this 에 부모 클래스 인스턴스 속성을 추가
return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}
TypeScript 의 Class 상속도 프로토타입 기반의 상속으로 컴파일 치환됩니다. 다음과 같습니다:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
// 부모 클래스의 정적 속성을 상속
extendStatics(d, b);
function __() { this.constructor = d; }
// 부모 클래스의 프로토타입 속성, 및 인스턴스 속성을 상속
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// __extends(A, B);
양자는 대동소이하며, 구현으로 보면, TypeScript 컴파일 산출물은 더욱 견고합니다. 그 목표는:
ES3+ 를 서포트하는 모든 호스트 환경에서 실행
P.S.비교재미있는 것은 정적 속성의 상속으로, 상세는 [一。어떻게 정적 속성을 상속하는가?](/articles/class 继承-es6 笔记 12/#articleHeader2) 참조
四.추상 클래스
TypeScript 에도 추상 클래스의 개념이 있습니다:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
추상 클래스에는 구현이 있는 구체적 메소드 (move 등) 도 있고, 선언만으로 구현하지 않는 추상 메소드 (makeSound 등) 도 있지만, 서브 클래스는 이러한 메소드를 구현해야 합니다:
class Cat extends Animal {
makeSound() {
console.log('meow meow meow');
}
}
또 하나의 유사한 개념은 인터페이스로, 양자의 차이는 인터페이스에서는「추상 메소드」만 정의할 수 있다는 것입니다 (abstract 키워드는 없으며, 정확히는 메소드 시그니처). 예를 들어:
interface Animal {
// abstract makeSound(): void; 와 비교
makeSound(): void;
}
// class Cat extends Animal 과 비교
class Cat implements Animal {
makeSound() {
console.log('meow meow meow');
}
}
五.클래스와 타입
클래스를 선언함과 동시에, 해당 클래스 인스턴스의 타입도 선언합니다. 예를 들어:
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
그 중에서, 인스턴스 greeter 는 Greeter 타입입니다. 즉, Class 선언은 타입 의미를 가집니다:
-
해당 클래스 인스턴스의 타입:
Greeter -
클래스 자신의 타입:
typeof Greeter
실제로, 클래스 자신의 타입은 정적 속성, 인스턴스 속성, 생성자, 프로토타입 메소드 등의 특징을 제약합니다. 예를 들어:
class GreeterDuck {
// 클래스 자신의 타입 제약
static standardGreeting = 'Hi';
greeting: string;
constructor(message: string) { }
greet() { return 'there'; }
// 많아도 되고 적어도 안 됨 (덕 타입)
sayHello() { /*...*/ }
}
let greeterType: typeof Greeter = GreeterDuck;
더 나아가:
// Class 타입에서 Interface 를 확장
interface HelloGreeter extends Greeter {
sayHello(): void;
}
let hGreeter: HelloGreeter = new GreeterDuck('world');
그렇습니다. 클래스는 타입 의미를 가지므로, 인터페이스는 클래스로부터 상속할 수 있습니다
아직 댓글이 없습니다