對于傳統(tǒng)的 JavaScript 程序我們會使用
函數
和基于原型
的繼承來創(chuàng)建可重用的組件僵刮,但對于熟悉使用面向對象方式的程序員使用這些語法就有些棘手瞎暑,因為他們用的是基于類的繼承
并且對象是由類構建出來的讲衫。 從 ECMAScript 2015卖丸,也就是 ES6 開始畔濒, JavaScript 程序員將能夠使用基于類的面向對象的方式顽悼。 使用 TypeScript孝赫,我們允許開發(fā)者現(xiàn)在就使用這些特性较木,并且編譯后的 JavaScript 可以在所有主流瀏覽器和平臺上運行,而不需要等到下個 JavaScript 版本青柄。
基本示例
下面看一個使用類的例子:
/*
類的基本定義與使用
*/
class Greeter {
// 聲明屬性
message: string
// 構造方法
constructor (message: string) {
this.message = message
}
// 一般方法
greet (): string {
return 'Hello ' + this.message
}
}
// 創(chuàng)建類的實例
const greeter = new Greeter('world')
// 調用實例的方法
console.log(greeter.greet())
如果你使用過 C# 或 Java伐债,你會對這種語法非常熟悉。 我們聲明一個 Greeter
類致开。這個類有 3 個成員:一個叫做 message
的屬性峰锁,一個構造函數和一個 greet
方法。
你會注意到双戳,我們在引用任何一個類成員的時候都用了 this
虹蒋。 它表示我們訪問的是類的成員。
后面一行飒货,我們使用 new
構造了 Greeter
類的一個實例魄衅。它會調用之前定義的構造函數,創(chuàng)建一個 Greeter
類型的新對象塘辅,并執(zhí)行構造函數初始化它晃虫。
最后一行通過 greeter
對象調用其 greet
方法
繼承
在 TypeScript 里浓镜,我們可以使用常用的面向對象模式肥败。 基于類的程序設計中一種最基本的模式是允許使用繼承來擴展現(xiàn)有的類栈妆。
看下面的例子:
/*
類的繼承
*/
class Animal {
run (distance: number) {
console.log(`Animal run ${distance}m`)
}
}
class Dog extends Animal {
cry () {
console.log('wang! wang!')
}
}
const dog = new Dog()
dog.cry()
dog.run(100) // 可以調用從父中繼承得到的方法
這個例子展示了最基本的繼承:類從基類中繼承了屬性和方法初肉。 這里,Dog
是一個 派生類荆责,它派生自 Animal
基類滥比,通過 extends
關鍵字。 派生類通常被稱作子類草巡,基類通常被稱作超類守呜。
因為 Dog
繼承了 Animal
的功能,因此我們可以創(chuàng)建一個 Dog
的實例山憨,它能夠 cry()
和 run()
查乒。
下面我們來看個更加復雜的例子:
class Animal {
name: string
constructor (name: string) {
this.name = name
}
run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Snake extends Animal {
constructor (name: string) {
// 調用父類型構造方法
super(name)
}
// 重寫父類型的方法
run (distance: number=5) {
console.log('sliding...')
super.run(distance)
}
}
class Horse extends Animal {
constructor (name: string) {
// 調用父類型構造方法
super(name)
}
// 重寫父類型的方法
run (distance: number=50) {
console.log('dashing...')
// 調用父類型的一般方法
super.run(distance)
}
xxx () {
console.log('xxx()')
}
}
const snake = new Snake('sn')
snake.run()
const horse = new Horse('ho')
horse.run()
// 父類型引用指向子類型的實例 ==> 多態(tài)
const tom: Animal = new Horse('ho22')
tom.run()
/* 如果子類型沒有擴展的方法, 可以讓子類型引用指向父類型的實例 */
const tom3: Snake = new Animal('tom3')
tom3.run()
/* 如果子類型有擴展的方法, 不能讓子類型引用指向父類型的實例 */
// const tom2: Horse = new Animal('tom2')
// tom2.run()
這個例子展示了一些上面沒有提到的特性。 這一次郁竟,我們使用 extends
關鍵字創(chuàng)建了 Animal
的兩個子類:Horse
和 Snake
玛迄。
與前一個例子的不同點是,派生類包含了一個構造函數棚亩,它 必須調用 super()
蓖议,它會執(zhí)行基類的構造函數。 而且讥蟆,在構造函數里訪問 this
的屬性之前勒虾,我們 一定要調用 super()
。 這個是 TypeScript 強制執(zhí)行的一條重要規(guī)則瘸彤。
這個例子演示了如何在子類里可以重寫父類的方法修然。Snake
類和 Horse
類都創(chuàng)建了 run
方法,它們重寫了從 Animal
繼承來的 run
方法质况,使得 run
方法根據不同的類而具有不同的功能愕宋。注意,即使 tom
被聲明為 Animal
類型结榄,但因為它的值是 Horse
中贝,調用 tom.run(34)
時,它會調用 Horse
里重寫的方法臼朗。
sliding...
sn run 5m
dashing...
ho run 50m
修飾符
默認為 public 修飾符
在上面的例子里邻寿,我們可以自由的訪問程序里定義的成員。 如果你對其它語言中的類比較了解依溯,就會注意到我們在之前的代碼里并沒有使用 public
來做修飾老厌;例如,C# 要求必須明確地使用 public
指定成員是可見的黎炉。 在 TypeScript 里枝秤,成員都默認為 public
。
你也可以明確的將一個成員標記成 public
慷嗜。 我們可以用下面的方式來重寫上面的 Animal
類:
private 修飾符
當成員被標記成 private
時淀弹,它就不能在聲明它的類的外部訪問丹壕。
protected 修飾符
protected
修飾符與 private
修飾符的行為很相似,但有一點不同薇溃,protected
成員在派生類中仍然可以訪問菌赖。例如:
/*
訪問修飾符: 用來描述類內部的屬性/方法的可訪問性
public: 默認值, 公開的外部也可以訪問
private: 只能類內部可以訪問
protected: 類內部和子類可以訪問
*/
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
public run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Person extends Animal {
private age: number = 18
protected sex: string = '男'
run (distance: number=5) {
console.log('Person jumping...')
super.run(distance)
}
}
class Student extends Person {
run (distance: number=6) {
console.log('Student jumping...')
console.log(this.sex) // 子類能看到父類中受保護的成員
// console.log(this.age) // 子類看不到父類中私有的成員
super.run(distance)
}
}
console.log(new Person('abc').name) // 公開的可見
// console.log(new Person('abc').sex) // 受保護的不可見
// console.log(new Person('abc').age) // 私有的不可見
readonly 修飾符
你可以使用 readonly
關鍵字將屬性設置為只讀的。 只讀屬性必須在聲明時或構造函數里被初始化沐序。
class Person {
readonly name: string = 'abc'
constructor(name: string) {
this.name = name
}
}
let john = new Person('John')
// john.name = 'peter' // error
參數屬性
在上面的例子中琉用,我們必須在 Person
類里定義一個只讀成員 name
和一個參數為 name
的構造函數,并且立刻將 name
的值賦給 this.name
策幼,這種情況經常會遇到邑时。 參數屬性可以方便地讓我們在一個地方定義并初始化一個成員。 下面的例子是對之前 Person
類的修改版特姐,使用了參數屬性:
class Person2 {
constructor(readonly name: string) {
}
}
const p = new Person2('jack')
console.log(p.name)
注意看我們是如何舍棄參數 name
晶丘,僅在構造函數里使用 readonly name: string
參數來創(chuàng)建和初始化 name
成員。 我們把聲明和賦值合并至一處唐含。
參數屬性通過給構造函數參數前面添加一個訪問限定符來聲明浅浮。使用 private
限定一個參數屬性會聲明并初始化一個私有成員;對于 public
和 protected
來說也是一樣捷枯。
存取器
TypeScript
支持通過 getters/setters
來截取對對象成員的訪問滚秩。 它能幫助你有效的控制對對象成員的訪問。
下面來看如何把一個簡單的類改寫成使用 get
和 set
淮捆。 首先叔遂,我們從一個沒有使用存取器的例子開始。
class Person {
firstName: string = 'A'
lastName: string = 'B'
get fullName () {
return this.firstName + '-' + this.lastName
}
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)
p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
靜態(tài)屬性
到目前為止争剿,我們只討論了類的實例成員,那些僅當類被實例化的時候才會被初始化的屬性痊末。 我們也可以創(chuàng)建類的靜態(tài)成員蚕苇,這些屬性存在于類本身上面而不是類的實例上。 在這個例子里凿叠,我們使用 static
定義 origin
涩笤,因為它是所有網格都會用到的屬性。 每個實例想要訪問這個屬性的時候盒件,都要在 origin
前面加上類名蹬碧。 如同在實例屬性上使用 this.xxx
來訪問屬性一樣,這里我們使用 Grid.xxx
來訪問靜態(tài)屬性炒刁。
/*
靜態(tài)屬性, 是類對象的屬性
非靜態(tài)屬性, 是類的實例對象的屬性
*/
class Person {
name1: string = 'A'
static name2: string = 'B'
}
console.log(Person.name2)
console.log(new Person().name1)
抽象類
抽象類做為其它派生類的基類使用恩沽。 它們不能被實例化。不同于接口翔始,抽象類可以包含成員的實現(xiàn)細節(jié)罗心。 abstract
關鍵字是用于定義抽象類和在抽象類內部定義抽象方法里伯。
/*
抽象類
不能創(chuàng)建實例對象, 只有實現(xiàn)類才能創(chuàng)建實例
可以包含未實現(xiàn)的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry()
dog.run()