介紹
傳統(tǒng)的JavaScript使用函數(shù)和基于原型的繼承來(lái)構(gòu)建可復(fù)用的組件,ES6引進(jìn)了基于類(lèi)的OO編程方法,TypeScript也是使用了最新的ES6語(yǔ)法進(jìn)行構(gòu)建的,它能將這些高級(jí)特性編譯成能在現(xiàn)代瀏覽器上運(yùn)行的JavaScript版本。
Classes特性
和
C#或Java類(lèi)似,包括屬性、方法、constructor(構(gòu)造函數(shù))成員訪問(wèn)符
this,我們?cè)陬?lèi)中用this語(yǔ)法訪問(wèn)類(lèi)中instance屬性
// eg
class Greeter {
greeting: string;
constructor(message: string) {
// this 成員訪問(wèn)符
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
- 通過(guò)
new方法創(chuàng)建類(lèi)的instance,這將調(diào)用我們?cè)陬?lèi)中定義的constructor(構(gòu)造函數(shù)),它將完成兩件事: 創(chuàng)建一個(gè)和Greeter類(lèi)一樣Shape的新對(duì)象,然后通過(guò)constructor里面的語(yǔ)句Initialize實(shí)例屬性
let greeter = new Greeter("world");
//
繼承
- 通過(guò)
extends關(guān)鍵字來(lái)繼承父類(lèi)
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
// 繼承
class Horse extends Animal {}
- 通過(guò)
super關(guān)鍵字調(diào)用父類(lèi)(base class)的constructor
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
- 通過(guò)
sub class和base class同名的方法,會(huì)重載(override)父類(lèi)的方法
class Horse extends Animal {
constructor(name: string) { super(name); }
// move 重載 父類(lèi)的move方法
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
訪問(wèn)修飾符
-
public修飾符
默認(rèn)設(shè)置為public,不需要顯式的寫(xiě)出(C#等語(yǔ)言需要顯式寫(xiě)出),這樣無(wú)論在子類(lèi)中還是類(lèi)外都可以無(wú)限制訪問(wèn)base class中的屬性或方法
class Animal {
//此時(shí) public也可以不寫(xiě)
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
-
private修飾符
如果在屬性或方法前面設(shè)置了private,就不能在任何sub class或類(lèi)外訪問(wèn)到這個(gè)屬性或方法
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private;
如果將base class的constructor加上private修飾符,那么派生類(lèi)也不不能調(diào)用super()方法訪問(wèn),類(lèi)外也不能實(shí)例化:
class Person {
protected name: string;
private constructor(theName: string) { this.name = theName; }
}
let john = new Person('John'); //Error, The constructor is private
TypeScript是一個(gè)基于結(jié)構(gòu)(shape)比較的類(lèi)型檢查系統(tǒng)(原文:是一種結(jié)構(gòu)類(lèi)型系統(tǒng),我為了更好的理解自作主張擴(kuò)充),也就是說(shuō):
如果類(lèi)中沒(méi)有private 或protected等修飾符,那么這個(gè)類(lèi)的成員和要檢查的類(lèi)型相同,那么它們兩類(lèi)型就是相同的。
class BigCar {
name: string;
constructor(theName: string) { this.name = theName; }
}
class SmallCar {
name: string;
constructor(theName: string) { this.name = theName; }
}
// 上面兩個(gè)類(lèi)是相同類(lèi)型的
反之,如果存在private 或protected那么除了檢查形狀(shape),還要檢查private 或protected后屬性或方法的申明源originated是否相同。
簡(jiǎn)而言之就是,它們的private 或protected后屬性或方法必須來(lái)源同一個(gè)申明。
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; // Error: 'Animal' and 'Employee' are not compatible
-
protected修飾符
除了能夠在(deriving classes)派生類(lèi),也就是子類(lèi)中訪問(wèn)到base class成員,其他的特性和private基本一致
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error
如果給constructor加上protected修飾符,那么base class只能在派生類(lèi)中被super()調(diào)用,在類(lèi)外不能實(shí)例化。
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee can extend Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected
只讀修飾符
-
readonly只能在定義時(shí)初始化,或者在constructor中初始化
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"; // error! name is readonly.
- 參數(shù)屬性:
Parameter properties。由于定義readonly屬性和在constructor中初始化是一個(gè)很常用的屬性,所以TypeScript做了一個(gè)簡(jiǎn)化,讓你在constructor中的parameter list前面加上readonly修飾符,那么在調(diào)用constructor創(chuàng)建一個(gè)新類(lèi)并初始化的時(shí)候就能同時(shí)創(chuàng)建一個(gè)readonly成員屬性,并初始化,而不用寫(xiě)在兩個(gè)地方。
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
//實(shí)際上readonly、public、private、protected都能獲得以上的create and initialize的效果
訪問(wèn)器Accessors
- TypeScript提供
getters / setters方法,使得你能精細(xì)的控制怎么對(duì)成員進(jìn)行操作。 - 通過(guò)攔截讀或?qū)懖僮?,?lái)避免不必要的bug
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
- 只設(shè)置了
get而沒(méi)有set那么這個(gè)屬性就是只讀的readonly - 只用ES5及以上的版本支持get / set
靜態(tài)屬性Static Properties
- 類(lèi)其實(shí)包括兩個(gè)方面:
instance side和static side,其中在屬性或方法前面加上了static以及constructor為static side, 其余為instance side -
static屬性通過(guò)類(lèi)名直接獲取,constructor通過(guò)new關(guān)鍵字創(chuàng)建實(shí)例
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) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
抽象類(lèi)Abstract classes
- 抽象類(lèi)只能是基類(lèi),不是繼承自其它類(lèi)或抽象類(lèi)。只能被繼承。
- 抽象類(lèi)不能直接實(shí)例化。
- 相比
Interface,抽象類(lèi)的成員可以包含具體的細(xì)節(jié),例如函數(shù)體的具體內(nèi)容。 - 抽象類(lèi)中加了
abstract關(guān)鍵字的省略了細(xì)節(jié),而且派生類(lèi)必須實(shí)現(xiàn)這些屬性。 - 抽象類(lèi)中可以沒(méi)有抽象屬性
- 有抽象屬性的類(lèi)一定是抽象類(lèi),必須帶上
abstract關(guān)鍵字
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // must be implemented in derived classes
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
高級(jí)技巧
- 構(gòu)造函數(shù)
Constructor functions
當(dāng)在TypeScript定義了一個(gè)類(lèi)的時(shí)候,實(shí)際上創(chuàng)建了兩份申明。 - 這個(gè)類(lèi)的實(shí)例類(lèi)型
- 這個(gè)類(lèi)的構(gòu)造函數(shù)
也就是說(shuō),創(chuàng)建一個(gè)類(lèi),用類(lèi)名賦予類(lèi)型檢查的時(shí)候是相當(dāng)于一個(gè)包含了這個(gè)類(lèi)所有實(shí)例成員的一個(gè)Interface
而類(lèi)名賦予變量,則相當(dāng)于把這個(gè)類(lèi)的構(gòu)造函數(shù)賦值給一個(gè)變量,所以用類(lèi)型檢查要用typeof 類(lèi)名
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
//用Greeter作為類(lèi)的實(shí)例的類(lèi)型來(lái)做類(lèi)型檢查
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
//typeof Greeter實(shí)際上類(lèi)的靜態(tài)屬性或方法的類(lèi)型來(lái)做類(lèi)型檢查
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
- 把類(lèi)當(dāng)做
interface來(lái)用
因?yàn)轭?lèi)名用作類(lèi)型檢查的作用和Interface類(lèi)似
綜上。接口可以繼承結(jié)構(gòu),結(jié)構(gòu)可以繼承類(lèi)。類(lèi)可以繼承類(lèi),類(lèi)可以實(shí)現(xiàn)接口。再加上一系列修飾符的限制,使得我們可以使用TypeScript完成很多豐富的操作。