前言 對於傳統的 JavaScript 程式我們會使用函數和基於原型的繼承來創建可重用的組件,但對於熟悉使用面向對象方式的程式員使用這些語法就有些棘手,因為他們用的是基於類的繼承並且對象是由類構建出來的。 從 ECMAScript 2015,也就是 ES6 開始, JavaScript 程式員將能夠 ...
前言
對於傳統的 JavaScript 程式我們會使用函數
和基於原型的繼承
來創建可重用的組件,但對於熟悉使用面向對象方式的程式員使用這些語法就有些棘手,因為他們用的是基於類的繼承並且對象是由類構建出來的。 從 ECMAScript 2015,也就是 ES6 開始, JavaScript 程式員將能夠使用基於類的面向對象的方式。 使用 TypeScript,我們允許開發者現在就使用這些特性,並且編譯後的 JavaScript 可以在所有主流瀏覽器和平臺上運行,而不需要等到下個 JavaScript 版本。
類
// 類
(() => {
class Person {
// 聲明屬性
name: string
age: number
gender: string
// 構造方法
constructor(name: string='jkc', age:number=18, gender:string='男') {
this.name = name
this.age = age
this.gender = gender
}
// 一般方法
sayHi(str: string){
console.log(`你好,我叫${this.name},今年${this.age}歲,性別${this.gender}, 我想說:`, str)
}
}
// 創建類的實例
const person = new Person()
// 調用實例的方法
person.sayHi('我很帥')
})()
如果你使用過C#或Java,你會對這種語法非常熟悉。我們聲明瞭一個Person
類。這個類有3個屬性、一個構造函數和一個sayHi
方法。
我們使用new
構造了Person
類的一個實例。它會調用構造函數,創建一個Person
類型的新對象,並執行構造函數初始化它。最後通過person
對象調用其sayHi
方法
繼承
在 TypeScript 里,我們可以使用常用的面向對象模式。 基於類的程式設計中一種最基本的模式是允許使用繼承來擴展現有的類。
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()
我們定義了一個超類Animal,兩個派生類Snake和Horse,並且創建了2個實例對象snake
和horse
。
通過snake.run()
,我們可以看到Snake中有run方法,那麼就進行調用,最後結果如下
通過horse.run()
,我們可以看到Horse中有run方法,那麼進行調用,最後結果如下:
多態
定義:不同類型的對象針對相同的方法,產生了不同的的行為
接著上面的代碼
// 父類型引用指向子類型的實例 ==> 多態
const tom: Animal = new Horse('ho22')
tom.run()
/* 如果子類型沒有擴展的方法, 可以讓子類型引用指向父類型的實例 */
const tom3: Snake = new Animal('tom3')
tom3.run()
/* 如果子類型有擴展的方法, 不能讓子類型引用指向父類型的實例 */
const tom2: Horse = new Animal('tom2')
tom2.run()
這個例子演示瞭如何在子類里可以重寫父類的方法。Snake
類和 Horse
類都創建了 run
方法,它們重寫了從 Animal
繼承來的 run
方法,使得 run
方法根據不同的類而具有不同的功能。註意,即使 tom
被聲明為 Animal
類型,但因為它的值是 Horse
,調用 tom.run(34)
時,它會調用 Horse
里重寫的方法。
公共,私有與受保護的修飾符
預設為public
在上面的例子里,我們可以自由的訪問程式里定義的成員。 如果你對其它語言中的類比較瞭解,就會註意到我們在之前的代碼里並沒有使用 public
來做修飾;例如,C#要求必須明確地使用 public
指定成員是可見的。 在TypeScript
里,成員都預設為 public
。
你也可以明確的將一個成員標記成 public
。 我們可以用下麵的方式來重寫上面的 Animal
類:
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
理解private
當成員被標記成 private時,它就不能在聲明它的類的外部訪問。比如:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 錯誤: 'name' 是私有的.
理解 protected
protected
修飾符與 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
參數來創建和初始化 name
成員。 我們把聲明和賦值合併至一處。
參數屬性通過給構造函數參數前面添加一個訪問限定符來聲明。使用 private
限定一個參數屬性會聲明並初始化一個私有成員;對於 public
和 protected
來說也是一樣。
存取器
TypeScript
支持通過 getters/setters
來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。
下麵來看如何把一個簡單的類改寫成使用 get
和 set
。 首先,我們從一個沒有使用存取器的例子開始。
class P{
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 P()
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)
p.fullName = 'E_F'
console.log(p.firstName, p.lastName)
靜態屬性
靜態成員:在類中通過static
修飾的屬性或方法,也就是靜態成員或靜態方法,靜態成員在使用時是通過類名.
的這種語法來調用
class People{
static name1: string = 'jkc'
// 構造函數是不能通過static修飾的
constructor() {
}
static sayHi() {
console.log("hello")
}
}
People.name1 = 'jkc2'
console.log(People.name1)
People.sayHi()
抽象類
抽象類:包含抽象方法(抽象方法一般沒有任何具體的內容的實現),也可以包含實例方法,抽象類是不能被實例化,為了讓子類進行實例化及實現內部的抽象方法。
abstract class P1 {
// 抽象方法不能有具體的實現代碼
abstract eat()
sayHi() {
console.log('hello')
}
}
class P2 extends P1 {
eat() {
// 重新實現抽象類中的方法,此時這個方式是P2的實例方法
console.log("吃東西")
}
}
const p2 = new P2()
p2.eat()