TypeScript類的高級技巧

類的基本示例

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的兩個子類:HorseSnake

與前一個例子的不同點是畸悬,派生類包含了一個構(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}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竟闪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杖狼,更是在濱河造成了極大的恐慌炼蛤,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝶涩,死亡現(xiàn)場離奇詭異理朋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绿聘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門嗽上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熄攘,你說我怎么就攤上這事兽愤。” “怎么了挪圾?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵浅萧,是天一觀的道長。 經(jīng)常有香客問我洛史,道長,這世上最難降的妖魔是什么酱吝? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任也殖,我火速辦了婚禮,結(jié)果婚禮上务热,老公的妹妹穿的比我還像新娘忆嗜。我一直安慰自己,他們只是感情好崎岂,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布捆毫。 她就那樣靜靜地躺著,像睡著了一般冲甘。 火紅的嫁衣襯著肌膚如雪绩卤。 梳的紋絲不亂的頭發(fā)上途样,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音濒憋,去河邊找鬼何暇。 笑死,一個胖子當(dāng)著我的面吹牛凛驮,可吹牛的內(nèi)容都是我干的裆站。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼黔夭,長吁一口氣:“原來是場噩夢啊……” “哼宏胯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起本姥,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肩袍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扣草,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體了牛,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年辰妙,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹰祸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡密浑,死狀恐怖蛙婴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尔破,我是刑警寧澤街图,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站懒构,受9級特大地震影響餐济,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胆剧,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一絮姆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秩霍,春花似錦篙悯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颠悬,卻和暖如春矮燎,著一層夾襖步出監(jiān)牢的瞬間定血,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工漏峰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留糠悼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓浅乔,卻偏偏與公主長得像倔喂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靖苇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容