類的基本示例
class Greeter {
greeting: string
constructor(message: string) {
this.greeting = message
}
greet() {
return 'Hello, ' + this.greeting
}
}
let greeter = new Greeter('world')
我們聲明一個 Greeter
類膘茎。這個類有 3 個成員:一個叫做 greeting
的屬性,一個constructor
構(gòu)造函數(shù)和一個 greet
方法脚草。
你會注意到赫悄,我們在引用任何一個類成員的時候都用了 this
。 它表示我們訪問的是類的成員馏慨。
最后一行埂淮,我們使用 new
構(gòu)造了Greeter
類的一個實例。它會調(diào)用之前定義的構(gòu)造函數(shù)写隶,創(chuàng)建一個 Greeter
類型的新對象倔撞,并執(zhí)行構(gòu)造函數(shù)初始化它。
繼承
在 TypeScript 里慕趴,我們可以使用常用的面向?qū)ο竽J健?基于類的程序設(shè)計中一種最基本的模式是允許使用繼承來擴(kuò)展現(xiàn)有的類痪蝇。
class Animal {
move(distance: number = 0) {
console.log(`Animal moved ${distance}m.`)
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!')
}
}
const dog = new Dog()
dog.bark()
dog.move(10)
這個例子展示了最基本的繼承:類從基類中繼承了屬性和方法。 這里冕房,Dog
是一個 派生類躏啰,它派生自 Animal
基類,通過 extends
關(guān)鍵字耙册。 派生類通常被稱作子類丙唧,基類通常被稱作超類。
因為 Dog
繼承了 Animal
的功能觅玻,因此我們可以創(chuàng)建一個 Dog
的實例想际,它能夠 bark()
和 move()
。
第二個例子:
class Animal {
name: string
constructor(name: string) {
this.name = name
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance}m.`)
}
}
class Snake extends Animal {
constructor(name: string) {
super(name)
}
move(distance: number = 5) {
console.log('Slithering...')
super.move(distance)
}
}
class Horse extends Animal {
constructor(name: string) {
super(name)
}
move(distance: number = 45) {
console.log('Galloping...')
super.move(distance)
}
}
let sam = new Snake('Sammy')
let tom: Animal = new Horse('Tommy')
sam.move()
tom.move(34)
這個例子展示了一些上面沒有提到的特性溪厘。 這一次胡本,我們使用 extends
關(guān)鍵字創(chuàng)建了 Animal
的兩個子類:Horse
和 Snake
。
與前一個例子的不同點是畸悬,派生類包含了一個構(gòu)造函數(shù)侧甫,它 必須調(diào)用 super()
,它會執(zhí)行基類的構(gòu)造函數(shù)。 而且披粟,在構(gòu)造函數(shù)里訪問 this 的屬性之前咒锻,我們 一定要調(diào)用 super()
。 這個是 TypeScript 強(qiáng)制執(zhí)行的一條重要規(guī)則守屉。
這個例子演示了如何在子類里可以重寫父類的方法惑艇。Snake類和 Horse 類都創(chuàng)建了 move 方法,它們重寫了從 Animal 繼承來的 move 方法拇泛,使得 move 方法根據(jù)不同的類而具有不同的功能滨巴。注意,即使 tom 被聲明為 Animal 類型俺叭,但因為它的值是 Horse恭取,調(diào)用 tom.move(34) 時,它會調(diào)用 Horse 里重寫的方法熄守。
公共蜈垮,私有與受保護(hù)的修飾符
默認(rèn)為public
在上面的例子里,我們可以自由的訪問程序里定義的成員裕照。 如果你對其它語言中的類比較了解窃款,就會注意到我們在之前的代碼里并沒有使用 public 來做修飾;
class Animal {
public name: string
public constructor(name: string) {
this.name = name
}
public move(distance: number) {
console.log(`${this.name} moved ${distance}m.`)
}
}
private
當(dāng)成員被標(biāo)記成 private 時牍氛,它就不能在聲明它的類的外部訪問。比如:
class Animal {
private name: string
constructor(name: string) {
this.name = name
}
}
new Animal('Cat').name // 錯誤: 'name' 是私有的.
如果其中一個類型里包含一個 private 成員烟阐,那么只有當(dāng)另外一個類型中也存在這樣一個 private 成員搬俊,并且它們都是來自同一處聲明時,我們才認(rèn)為這兩個類型是兼容的蜒茄。
class Animal {
private name: string
constructor(name: string) {
this.name = name
}
}
class Rhino extends Animal {
constructor() {
super('Rhino')
}
}
class Employee {
private name: string
constructor(name: string) {
this.name = name
}
}
let animal = new Animal('Goat')
let rhino = new Rhino()
let employee = new Employee('Bob')
animal = rhino
animal = employee // 錯誤: Animal 與 Employee 不兼容.
protected:受保護(hù)修飾符
class Person {
// protected: 受保護(hù)成員唉擂,只能在子類中使用
protected name: string
//protected: 不可被 new Person()
protected constructor(name: string) {
this.name = name
}
}
class Employee extends Person {
private department: string
constructor(name: string, department: string) {
super(name)
this.department = department
}
getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}`
}
}
let howard = new Employee('Howard', 'Sales')
console.log(howard.getElevatorPitch())
構(gòu)造函數(shù)也可以被標(biāo)記成 protected。 這意味著這個類不能在包含它的類外被實例化檀葛,但是能被繼承玩祟。
readonly修飾符
使用 readonly 關(guān)鍵字將屬性設(shè)置為只讀的。 只讀屬性必須在聲明時或構(gòu)造函數(shù)里被初始化屿聋。
class Person {
readonly name: string
constructor(name: string) {
this.name = name
}
}
let john = new Person('john')
john.name = '' //error:不可以對只讀屬性賦值
// 給參數(shù)加上readonly修飾符空扎,同樣不可修改
class Person {
constructor(readonly name: string) {
}
}
let bob = new Person('Bob')
console.log(bob.name)
bob.name = '' //報錯
存取器
TypeScript 支持通過 getters/setters 來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問润讥。
下面例子是驗證密碼转锈,然后允許修改name信息
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)
}
我們可以修改一下密碼,來驗證一下存取器是否是工作的楚殿。當(dāng)密碼不對時撮慨,會提示我們沒有權(quán)限去修改員工。
對于存取器有下面幾點需要注意的:
首先,存取器要求你將編譯器設(shè)置為輸出 ECMAScript 5 或更高砌溺。 不支持降級到 ECMAScript 3影涉。其次,只帶有 get 不帶有 set 的存取器自動被推斷為 readonly规伐。這在從代碼生成 .d.ts 文件時是有幫助的蟹倾,因為利用這個屬性的用戶會看到不允許夠改變它的值
靜態(tài)屬性
創(chuàng)建類的靜態(tài)成員,這些屬性存在于類本身上面而不是類的實例上楷力。 在這個例子里喊式,我們使用 static
定義 origin
,因為它是所有網(wǎng)格都會用到的屬性萧朝。 每個實例想要訪問這個屬性的時候岔留,都要在 origin 前面加上類名。 如同在實例屬性上使用 this.xxx
來訪問屬性一樣检柬,這里我們使用 Grid.xxx
來訪問靜態(tài)屬性献联。
// 創(chuàng)建網(wǎng)格類
class Grid {
static origin = { x: 0, y: 0 }
scale: number
constructor(scale: number) {
this.scale = scale
}
// 計算點距離
calculateDistanceFromOrigin(point: { x: number, y: number }) {
// 計算坐標(biāo)差
let xDist = point.x - Grid.origin.x
let yDist = point.y - Grid.origin.y
return Math.sqrt(xDist * xDist + yDist * yDist) * this.scale
}
}
let grid1 = new Grid(1.0) // 傳入縮放比例
let grid2 = new Grid(5.0)
console.log(grid1.calculateDistanceFromOrigin({ x: 3, y: 4 })) // 5
console.log(grid2.calculateDistanceFromOrigin({ x: 3, y: 4 })) // 25
抽象類
抽象類做為其它派生類的基類使用。 它們一般不會直接被實例化何址。不同于接口里逆,抽象類可以包含成員的實現(xiàn)細(xì)節(jié)。 abstract
關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法用爪。
// abstract 定義抽象類
abstract class Animal {
// abstract 抽象方法
abstract makeSound(): void
move(): void {
console.log('roaming the earth...')
}
}
抽象類中的抽象方法不包含具體實現(xiàn)并且必須在派生類中實現(xiàn)原押。 抽象方法的語法與接口方法相似。兩者都是定義方法簽名但不包含方法體偎血。 然而诸衔,抽象方法必須包含 abstract 關(guān)鍵字并且可以包含訪問修飾符。
abstract class Department {
name: string
constructor(name: string) {
this.name = name
}
printName(): void {
console.log(`Department name ${this.name}`)
}
// 定義函數(shù)簽名颇玷,在派生類中具體實現(xiàn)
abstract printMeeting(): void
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing')// 在派生類的構(gòu)造函數(shù)中必須調(diào)用 super()
}
// 實現(xiàn)抽象方法
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.')
}
// 定義自己的成員方法
generateReports(): void {
console.log('Generating accounting reports...')
}
}
let department: Department // 允許創(chuàng)建一個對抽象類型的引用
department = new Department() // 錯誤: 不能創(chuàng)建一個抽象類的實例
department = new AccountingDepartment() // 允許對一個抽象子類進(jìn)行實例化和賦值
department.printName()
department.printMeeting()
department.generateReports() // 錯誤: 方法在聲明的抽象類中不存在
高級技巧
構(gòu)造函數(shù)
當(dāng)你在 TypeScript 里聲明了一個類的時候笨农,實際上同時聲明了很多東西。首先就是類的實例的類型
class Greeter {
static standardGreeting = 'Hello, there'
greeting: string
constructor(message: string) {
this.greeting = message
}
greet() {
return 'Hello, ' + this.greeting
}
}
// 我們寫了 let greeter: Greeter帖渠,意思是 Greeter 類的實例的類型是 Greeter谒亦。
let greeter: Greeter
greeter = new Greeter('world')
console.log(greeter.greet())
稍微改寫一下這個例子
class Greeter {
static standardGreeting = 'Hello, there'
greeting: string
constructor(message?: string) {
this.greeting = message
}
greet() {
if (this.greeting) {
return `Hello, ${this.greeting}`
} else {
return Greeter.standardGreeting
}
}
}
let greeter: Greeter
greeter = new Greeter()
console.log(greeter.greet())//Hello, there
// 使用 typeof 聲明 greeterMaker 是 Greeter的類的靜態(tài)類型
let greeterMaker: typeof Greeter = Greeter
// 接下來可以修改靜態(tài)屬性
greeterMaker.standardGreeting = 'Hey there'
let greeter2 = new greeterMaker()
console.log(greeter.greet())//Hey there
我們直接使用類。 我們創(chuàng)建了一個叫做 greeterMaker
的變量空郊。這個變量保存了這個類或者說保存了類構(gòu)造函數(shù)份招。 然后我們使用 typeof Greeter
,意思是取 Greeter
類的類型狞甚,而不是實例的類型脾还。或者更確切的說入愧,"告訴我 Greeter
標(biāo)識符的類型"鄙漏,也就是構(gòu)造函數(shù)的類型嗤谚。 這個類型包含了類的所有靜態(tài)成員和構(gòu)造函數(shù)。 之后怔蚌,就和前面一樣巩步,我們在 greeterMaker
上使用 new
,創(chuàng)建 Greeter
的實例桦踊。
把類當(dāng)做接口使用
類定義會創(chuàng)建兩個東西:類的實例類型和一個構(gòu)造函數(shù)椅野。 因為類可以創(chuàng)建出類型,所以你能夠在允許使用接口的地方使用類籍胯。
class Point {
x: number
y: number
}
// extends使Point3d可以共享 Point下的屬性
interface Point3d extends Point {
z: number
}
let point3d: Point3d = {x: 1, y: 2, z: 3}