第六節(jié):TypeScript類

Class 類

TypeScript 支持ES2015中引入的關(guān)鍵字class

與其他JavaScript語言功能一樣, TypeScript也為class添加了類型注釋和其他語法, 以允許你表達類和其他類型之間的關(guān)系


1. class 成員

這是一個基本的類,只是一個空類

class Point{
    
}

這個類還不是很有用,所以我們開始添加一些成員


1.1. 字段

1.1.1 字段(屬性)的理解
字段聲明在類上創(chuàng)建公共可寫屬性
class Point{
    // 這些字段在在類實例化后會成為實例對象的屬性
    x: number  
    y: number
}

// 實例化
const pt = new Point()

pt.x = 0;
pt.y = 0;
console.log('pt',pt)
/*
    {x: 0,y: 0}
*/

類的成員x, y為公共的可寫屬性, 也就是類實例上的屬性


與其他位置一樣, 類型注釋是可選的, 但是如果未指定,將默認為any類型

class Point{
    x;    // 類型: (property) Point.x: any
    y;    // 類型: (property) Point.y: any
}


字段也可以有初始器, 這些將在類被實例化時自動運行

class Point{
    x = 0;
    y = 0;
    // (property) Point.x: number
    // (property) Point.y: number
}

const pt = new Point()
console.log(`${pt.x}, ${pt.y}`); // 0 ,0

就像var,const, let定義變量一樣, 類屬性的初始器也將會用于推斷其類型

如果賦值類型不匹配將報錯

class Point{
    x = 0;
    y = 0;
}

const pt = new Point()
pt.x = '0'
// 不能將類型“string”分配給類型“number”

初始化賦值為number類型的值, 實例化后賦予string類型的值將警告


1.1.2 readonly 只讀

字段可以使用readonly修飾符為前綴, 這樣可以防止對構(gòu)造函數(shù)之外的字段進行賦值

例如:

class Greeter {
    readonly name: string = 'world'

    constructor(otherName?:string){
        if(otherName !== undefined){
            this.name = otherName
        }
    }

    err(){
        this.name = 'not ok'
        // 報錯:無法分配到 "name" ,因為它是只讀屬性枚碗。
    }
}

const g = new Greeter()
g.name = 'also not ok'
// 無法分配到 "name" 襟齿,因為它是只讀屬性。


只讀屬性值可以在constructor構(gòu)造函數(shù)中進行修改賦值

例如:

const h = new Greeter('world')
console.log('h',h)
// {name: 'world'}

類中初始namehello在構(gòu)造函數(shù)constructor被調(diào)用時修改為world


1.2 構(gòu)造函數(shù)

類的構(gòu)造函數(shù)與普通函數(shù)非常相似, 你可以添加帶有類型注釋, 默認值和重載的參數(shù)

例如:

class Person{
    name:string;
    age:number;

    // 構(gòu)造函數(shù)
    // name 參數(shù)添加了類型注釋
    // age 參數(shù)使用了默認值, 自動推斷類型為number
    constructor(name:string, age = 18){
        // (parameter) age: number
        this.name = name  
        this.age = age
    }
}

在構(gòu)造函數(shù)中, name屬性, 添加了類型注釋, age屬性添加了默認值


構(gòu)造函數(shù)也可以使用重載

class Person{
    name:string;
    age:number;
    className: number;  // 班級

    // 重載
    // 定義了兩個重載, 一個接受兩個參數(shù), 第二個接受一個number類型參數(shù)
    constructor(name:string,age:number)
    constructor(className:number);
    // 重載的實現(xiàn)
    constructor(nameOrClassName:string | number, age = 18){
        if(typeof nameOrClassName == 'string'){
            this.name =  nameOrClassName
        }else{
            this.className = nameOrClassName
        }

        this.age = age
    }
}

類構(gòu)造函數(shù)簽名和函數(shù)簽名之間只有一些區(qū)別:

  1. 構(gòu)造函數(shù)不能有類型參數(shù)-他們屬于外部類聲明, 稍后了解
  2. 構(gòu)造函數(shù)不能有返回類型注釋- 類實例類型總是返回的


構(gòu)造函數(shù)中super 關(guān)鍵字

注意,就像在JavaScript中一樣, 如果有一個基類, 你需要使用super()來繼承基類的屬性或方法時, 在 任何this.成員之前調(diào)用你的構(gòu)造函數(shù)體

例如

// 基類
class Base{
    age = 16
}


// 派生類
class Person extends Base{

    constructor(){
        // 錯誤的寫法:訪問派生類的構(gòu)造函數(shù)中的 "this" 前寞冯,必須調(diào)用 "super"院水。
        this.age = 20
        super()
    }
}

在JavaScript中, 忘記調(diào)用super是一個很容易犯的錯誤, 但TypeScript會在必要時告訴你

例如:

// 派生類
class Person extends Base{

    constructor(){
        this.age = 20
        // 不使用super 報錯:
        // 派生類的構(gòu)造函數(shù)必須包含 "super" 調(diào)用。
    }
}


1.3 方法

類上的函數(shù)屬性稱為方法, 方法可以使用所有與函數(shù)和構(gòu)造函數(shù)相同的類型注釋

例如:

class Person{
    name:string
    age: number

    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }

    // 方法(類實例化后,原型上的方法)
    sayHello(name:string):void{
        console.log(`hello ${name}, 我叫${this.name}`)
    }
}

const student = new Person('張三',18)
student.sayHello('李四')
// console: hello 李四, 我叫張三

除了標準的類型注釋, TypeScript沒有為方法添加任何新的東西


請注意,在方法體內(nèi), 仍然必須通過this.的方式調(diào)用主體中非限定名稱的成員, 將始終引用封閉范圍內(nèi)的某些內(nèi)容

例如:

let x:number = 10
class C{
    x:string = 'hello'

    m(){
        x = 'world'
        // 不能將類型“string”分配給類型“number”简十。
        // 不使用this.成員的方式, 直接使用x 查找的不是類成員,而是不同的變量x
    }
}

示例中調(diào)用的x并不是類主體范圍內(nèi)的x屬性, 而是外部的變量x, 因此不能通過將string類型的值賦值給number類型的變量


1.4 getters / setters

類也可以有訪問器

例如:

class C{
    _length = 0;

    // getter
    get length(){
        return this._length
    }

    // setter
    set length(value){
        this._length = value
    }
}

請注意檬某,沒有額外邏輯的由字段支持的 get/set 對在 JavaScript 中很少有用。如果您不需要在 get/set 操作期間添加其他邏輯螟蝙,則可以公開公共字段恢恼。


Typescript對訪問器有一些特殊的推理規(guī)則

  1. 如果get存在但不存在set, 則屬性自動推斷為readonly屬性
  2. 如果不指定setter參數(shù)的類型, 則從getter的返回類型推斷
  3. GetterSetter必須具有相同成員可見性


TypeScript4.3開始, 可以使用不同類型的訪問器來獲取和設(shè)置

例如:

class Thing {
  _size = 0;
 
  get size(): number {
    return this._size;
  }
 
  set size(value: string | number | boolean) {
    let num = Number(value);
 
    // Don't allow NaN, Infinity, etc
 
    if (!Number.isFinite(num)) {
      this._size = 0;
      return;
    }
 
    this._size = num;
  }
}

示例中, 在使用訪問器設(shè)置參數(shù)可以為string, number, boolean的聯(lián)合類型

但是因為屬性是number類型, 所有需要在訪問器中通過Number 轉(zhuǎn)換類型


1.5 索引簽名

類可以聲明索引簽名; 這與 其他對象類型的索引簽名相同

處理構(gòu)造函數(shù), 類型中的成員都必須滿足索引簽名

例如:

class MyClass{
    // 索引簽名: 屬性的值為Boolean類型或一個方法接受一個string類型參數(shù)并返回boolean類型
    [s:string]: boolean | ((s:string) => boolean)

    // 屬性
    isName = false

    // 方法
    check(s:string){
        return true
    }
}


如果不滿足索引簽名的匹配則報錯

class MyClass{
    // 索引簽名: 屬性的值為Boolean類型或一個方法接受一個string類型參數(shù)并返回boolean類型
    [s:string]: boolean | ((s:string) => boolean)

    // 屬性
    isName = 10
    // 報錯: 類型“number”的屬性“isName”不能賦給“string”索引類型“boolean | ((s: string) => boolean)”。ts(2411)

    // 方法
    check(s:string){
        return this[s]
    }
    // 報錯:類型“(s: string) => boolean | ((s: string) => boolean)”的屬性“check”不能賦給“string”索引類型“boolean | ((s: string) => boolean)”
}

索引簽名類型還需要捕獲方法的類型, 所以要有效的使用這些類型并不容易,

通常最好將索引數(shù)據(jù)存儲在了另一個地方而不是類實例 本身


2. 類的實現(xiàn)

2.1implements實現(xiàn)

你可以使用implements關(guān)鍵字來檢查一個類是否滿足特定接口interface, 如果一個類未能正確實現(xiàn)這個接口, 則會發(fā)錯錯誤

簡單說:就是類中成員要實現(xiàn)接口定義成員

例如:

// 接口中聲明了一個方法
interface Person{
    name: string  //接口中的屬性
    sayHello():void  // 接口中的方法
}

// Student 類通過 implements 實現(xiàn)接口 Person 成員
class Student implements Person{
    // 實現(xiàn)接口中的屬性
    name :string

    // 實現(xiàn)接口中的方法
    sayHello() {
        console.log('hello')
    }
}

// Doctor 未實現(xiàn) 接口中的sayHello 方法 ,因此 報錯
class Doctor implements Person{
    say() {
        console.log('hello')
    }
}
/*
  類“Doctor”錯誤實現(xiàn)接口“Person”胰默。
  類型 "Doctor" 中缺少屬性 "sayHello"场斑,但類型 "Person" 中需要該屬性。
*/

示例中Student實現(xiàn)了接口Person, 但是Doctor類沒有實現(xiàn), 所有報錯, 告訴接口中需要sayHello屬性, 但Doctor沒有實現(xiàn)它


2.2 類實現(xiàn)多個接口

類也可以實現(xiàn)多個接口, 語法如下:

class C implements A, B {}
// C為類, A,B為接口

例如:

// 接口中聲明了一個方法
interface Person{
    sayHello():void
}

// 接口
interface Base{
    name:string;
    age: number;
}

// 類實現(xiàn)兩個接口
class Student implements Person,Base{
    // name,age 實現(xiàn) Base接口
    name = 'hello';
    age = 18

    // sayHello 實現(xiàn)Person 接口
    sayHello() {
        console.log('hello')
    }
}


注意事項:

一個很重要的理解:implements關(guān)鍵字只是檢查類是否可以被視為接口類型, 它根本不會改變類的類型或其他方法,一個常見的錯誤就是假設(shè)implement關(guān)鍵字會改變類的類型, 但是它不會

例如:

// 接口
interface Checkable{
  check(name:string):boolean
}

class NameCheck implements Checkable{
  check(s) {
      // (parameter) s: any
      // 注意此時參數(shù)那么為any類型, 并不會因為implements, 而導(dǎo)致參數(shù)類型發(fā)生改變(變?yōu)榻涌诙x的參數(shù)類型) 
     return s.toLowercase() === 'ok'
  }
}

在這個例子中, 我們可以預(yù)計參數(shù)s的類型會受到接口中name:string參數(shù)的影響, 但implements不會更改檢查類主體或推斷其類型的方式

因此示例中,參數(shù)s還是按照正常的推斷規(guī)則, 被推斷為any類型


2.3 接口中存在可選屬性

同樣, 如果 接口中使用了可選屬性,那么在實現(xiàn)接口時不會創(chuàng)建該屬性

// 接口,age為可選屬性
interface Person{
  name: string
  age?:number
}

// 1.類實現(xiàn), age可選屬性沒有被實現(xiàn)
class Student implements Person{
  name: string
}

// 實例化類
const stu = new Student()
// 實例對象上不存在age屬性, 因為類沒有實現(xiàn)age屬性
stu.age = 10

// Doctror實現(xiàn)了接口中的可選屬性
class Doctor implements Person{
    name: string
    age:number
}


// 2.類實現(xiàn)了可選屬性,實例對象上就可以操作可選屬性
const doc = new Doctor()
doc.age = 20


3. extends 類繼承

與其他具有面向?qū)ο筇匦缘恼Z言一樣牵署,JavaScript 中的類可以從基類繼承漏隐。

類可能extends 繼承基類, 派生類具有其基類的所有屬性和方法, 并且還可以定義其他成員

例如:

// 基類
class Animal{
  move(){
    console.log('Moving along')
  }
}

// 派生類
class Dog extends Animal{
  woof(times:number){
    for(let i = 0; i< times; i++ ){
      console.log('woof!')
    }
  }
}

const dog = new Dog()
dog.move()
// Moving along
dog.woof(3)
// 3 woof!

示例中Dog類雖然沒有定義move 方法, 但是其實例對象依然可以使用move方法

原因在于Dog類是extends繼承基類Animal類的派生類, 其會自動繼承基類的成員


3.1 覆蓋方法

派生類也可以覆蓋其基類字段或?qū)傩? 你可以使用super語法來訪問基類方法,

TypeScript強制派生類始終是其基類的子類型

例如, 下面的這種覆蓋方法是合法的方式

// 基類
class Base{
  greet(){
    console.log('hello world')
  }
}

// 派生類
class Derived extends Base {
  greet(name?:string){
    if(name === undefined){
      // 如果沒有傳遞參數(shù), 則調(diào)用父類的greet方法
      super.greet()
    }else{
      // 如果有參數(shù), 則使用派生類自己的覆蓋
      console.log(`Hello, ${name.toUpperCase()}`)
    }
  }
}

const d = new Derived()
d.greet()
// hello world
d.greet('reader')
// Hello, READER


派生類遵循其基類契約很重要, 請記住, 通過基類類型注釋來引用派生類的實例是很常見的(而且總是合法的)

// 通過基類類型注釋來應(yīng)用派生類的實例
const b:Base = d;
b.greet()
// hello world
b.greet('jack')
// Hello, JACK

示例中,變量b的類型注釋是基類Base, 但是賦值給變量b的確實派生類的實例對象


如果派生類不遵循基類的契約怎么辦?

// 基類
class Base{
  greet(){
    console.log('hello world')
  }
}

// 派生類
class Derived extends Base {
  greet(name:string){
    console.log(`Hello, ${name.toUpperCase()}`) 
  }
}

// 錯誤:
/*
  類型“Derived”中的屬性“greet”不可分配給基類型“Base”中的同一屬性。
  不能將類型“(name: string) => void”分配給類型“() => void”
*/

示例中: 基類Base中g(shù)reet 方法是沒有參數(shù)的, 但是派生類Derived中greet方法的參數(shù)是必傳參數(shù),

此時TypeScript就會發(fā)出錯誤, 提示派生類中的屬性greet不可分配給基類Base中的同一屬性


如果此時還將派生類的實例賦值給使用基類類型注釋的變量, 也將會發(fā)出錯誤

const b: Base = new Derived();
/*
  錯誤:
    不能將類型“Derived”分配給類型“Base”奴迅。
    屬性“greet”的類型不兼容青责。
    不能將類型“(name: string) => void”分配給類型“() => void”
*/

示例中報錯, 因為派生類和基類中的greet屬性不兼容


3.2 僅類型字段聲明

當(dāng)target >= ES2022或者useDefinedForClassFields這是為ture 時, 類字段在父類構(gòu)造函數(shù)完成后初始化

覆蓋父類設(shè)置的任何值, 當(dāng)你只想為繼承的字段重新聲明更為精確的類型時, 這可能會成為問題

為了處理這種情況, 你可以使用declare向TypeScript表明這個字段聲明不應(yīng)該有運行時影響

例如:

// 接口
interface Animal{
  dataOfBirth:any
}


// 接口擴展
interface Dog extends Animal{
  breed: any
}

// 基類
class AnimalHouse{
  resident: Animal;

  constructor(animal:Animal){
    this.resident = animal
  }
}

// 派生類
class DogHouse extends AnimalHouse{
  //  不會對JavaScript 代碼運行有任何影響
  // 只是讓屬性的類型更加精準
  declare resident:Dog;
  constructor(dog: Dog){
    super(dog)
  }
}


3.3 初始化順序

在某些情況下, JavaScript類的初始化順序可能會令人驚訝,

例如:

// 基類
class Base{
  name = 'base'
  constructor(){
    console.log('My name is '+ this.name)
  }
}

// 派生類
class Derived extends Base{
  name = 'derived'
}

const d  = new Derived()
// 打印: My name is base

示例中答應(yīng)name這是base, 而不是derived


那這里發(fā)生了什么?

JavaScript定義的類初始化順序是:

  1. 基類字段被初始化 name = 'base'
  2. 基類構(gòu)造函數(shù)運行, Base中constructor 執(zhí)行
  3. 派生類字段被初始化: name = 'derived'
  4. 派生構(gòu)造函數(shù)運行

這意味著基類構(gòu)造函數(shù)name在其自己的構(gòu)造函數(shù)中看到了自己的值, 因為此時派生類字段初始化尚未運行


4. 類成員可見性

你可以使用TypeScript來控制某些方法或?qū)傩允欠駥︻愅獠康拇a可見


4.1 public 公共

類成員默認可見性是public, 可以在任何地方訪問成員

例如

// 類
class Greeter{
  public greet(){
    console.log('hello')
  }
}

// 實例
const g = new Greeter()
g.greet()

因為public可見性修飾符 已經(jīng)是默認的, 所以你不需要在 類的成員上編寫此修飾符, 但有時可能出于樣式/可讀性的原因肯能會選擇添加


4.2 protected 受保護的

protected成員僅對聲明它們的類以及當(dāng)前類的子類可見


// 類
class Greeter{
  // 公共的
  public greet(){
    console.log('hello, ' + this.getName())
  }

  // 受保護的
  protected getName(){
    return 'jack'
  }
}

// 派生類(子類)
class SpecialGreeter extends Greeter{
  public howdy(){
    console.log('howdy, ' + this.getName())
  }
}

// 基類的實例
const g = new Greeter()
g.greet()
g.getName()
// 錯誤: 屬性“getName”受保護,只能在類“Greeter”及其子類中訪問

// 派生類(子類)實例
const s = new SpecialGreeter()
s.howdy()
s.getName()
// 錯誤: 屬性“getName”受保護取具,只能在類“Greeter”及其子類中訪問

通過示例了解protected修飾的屬性和方法,只能在類內(nèi)部使用, 不能再類的實例上調(diào)用


protected 成員曝光

派生類需要遵循其基類的契約, 但可以選擇公開具有更過功能的的基類子類型, 這包括是protected成員public

例如:

// 基類
class Base{
  protected num = 10
}

// 派生類
class Dervied extends Base{
  // 沒有任何修飾符, 當(dāng)前num就是public
  num = 50
}

const d = new Dervied()
console.log(d.num)  // 50

請注意, Dervied類中的num屬性已經(jīng)可以自由讀寫, 因此這要要注意, protected修飾的屬性主要在派生類中,如果沒有添加修飾, 當(dāng)前同名屬性將變?yōu)?code>public, 如果這種暴露不是 故意的, 那么我們就需要小心重復(fù)修飾符

<>

4.3 private 私有的

private 就像protected, 但不允許子類訪問該成員, 只能在當(dāng)前類中訪問

例如:

// 基類
class Base{
  private num = 10
}

// 派生類
class Dervied extends Base{
  showNum(){
    console.log(this.num)
    // 屬性“num”為私有屬性脖隶,只能在類“Base”中訪問。
  }
}

// 基類實例化
const b = new Base()
console.log(b.num)
// 屬性“num”為私有屬性暇检,只能在類“Base”中訪問

// 派生類實例化
const d = new Dervied()
d.showNum()

示例中,發(fā)現(xiàn)private修飾的屬性為私有屬性, 只能在當(dāng)前類中訪問, 和protected很像

protected修飾符的屬性為受保護的屬性,只能在類中訪問,可以是當(dāng)前類也是派生類


因為private 成員對派生類不可見, 所以派生類不能增加其可見性(否則報錯)

// 基類
class Base{
  private num = 10
  showNum(){
    console.log(this.num)
  }
}

// 派生類
class Dervied extends Base{
  num = 20
}

/*
  類“Dervied”錯誤擴展基類“Base”产阱。
  屬性“num”在類型“Base”中是私有屬性,但在類型“Dervied”中不是
*/


跨實例訪問private 成員

TypeScript允許跨實例訪問private 成員

class A{
  private x = 10;
  
  sameAs(other:A){
    return other.x === this.x
  }
}

// 實例一
const a = new A()

// 實例二
const b = new A()

const bol = b.sameAs(a)
console.log('bol',bol)
// bol true


注意事項:

與TypeScript類型系統(tǒng)的其他方面一樣,private,protected只在類型檢查期間強制執(zhí)行

這意味著像in或者簡單的JavaScript運行時構(gòu)造的屬性查找任然可以訪問private, protected成員

// 類
class MySafe{
  private num = 123
}

// 實例
const s = new MySafe()
console.log(s.num)
// TypeScript報錯: 屬性“num”為私有屬性块仆,只能在類“MySafe”中訪問构蹬。
// 編譯后, JavaScript運行, 依然可以答應(yīng)出 123

private還允許在類型檢查期間使用括號表示法進行訪問。這使得private-declared 字段可能更容易訪問單元測試等內(nèi)容悔据,缺點是這些字段是軟私有的并且不嚴格執(zhí)行隱私庄敛。

class MySafe{
  private num = 123
}

const s = new MySafe()
// 通過中括號訪問不報錯
console.log(s['num']) // 123


與TypeScript的private修飾符不同, JavaScript的私有字段(#) 在編譯后仍然是私有的, 并且不提供前面提到的轉(zhuǎn)移艙口(如括號符號訪問), 這使得他們成為了硬私有的

class Person {
  #num = 10;
  name = '張三';
  constructor(){ }
}


// 實例化
const student = new Person()
console.log(student['#num'])  // undefined 獲取不到值

如果您需要保護類中的值免受惡意行為者的侵害,您應(yīng)該使用提供硬運行時隱私的機制蜜暑,例如閉包铐姚、WeakMaps 或私有字段。請注意,這些在運行時添加的隱私檢查可能會影響性能隐绵。


5. 靜態(tài)成員

類可能有static成員, 這些成員不與類的特定實例相關(guān)聯(lián), 他們可以通過類構(gòu)造函數(shù)對象本身訪問:

例如:

// 類
class MyClass{
  static num = 10;
  static printNum(){
    console.log(MyClass.num)
  }
}

// 通過類名訪問static靜態(tài)成員
console.log(MyClass.num)
MyClass.printNum()


靜態(tài)成員也可以使用相同的public, protected, 以及private修飾符

// 類
class MyClass{
  // 靜態(tài)屬性num 使用 了私有private 修飾符
  // 此時num 只能在MyClass類中訪問
  private static num = 10;
  static printNum(){
    // ok
    console.log(MyClass.num)
  }
}



console.log(MyClass.num) 
// 錯誤: 屬性“num”為私有屬性之众,只能在類“MyClass”中訪問。

MyClass.printNum()


靜態(tài)成員也會被繼承

// 基類
class Base{
  static getGreeting(){
    return 'hello world'
  }
}

// 派生類
class Derived extends Base{
  myGreeting = Derived.getGreeting()
}


5.1 特殊靜態(tài)名稱

Function從原型覆蓋屬性通常是不安全/不可能的, 因為類本省就是可以調(diào)用的函數(shù). 所以不能將諸如類的名稱, name, length, 和類函數(shù)屬性call 定義為static成員

class Person{
  static name = 'hello'
  // 靜態(tài)屬性“name”與構(gòu)造函數(shù)“Person”的內(nèi)置屬性函數(shù)“name”沖突
}


6.類中的 static 塊(代碼塊)

靜態(tài)塊允許你編寫具有自己范圍的語句序列,這些語句可以訪問包含類中的私有字段, 這意味著我們可以編寫具有編寫語句的所有功能的初始代碼, 不會泄露變量, 并且可以完全訪問我們類的內(nèi)部結(jié)構(gòu)

例如:

// 類型
class Foo{
  static #count = 0

  get count(){
    return Foo.#count;
  }

  static {
    // 靜態(tài)快
    try {
      const num =  Math.floor(Math.random() * 10 )
      Foo.#count += num
  
    }catch(error){

    }
  }
}


7. 泛型類

類, 很像接口, 可以是泛型的,當(dāng)使用實例化泛型類時, 其類型參數(shù)的推斷方式與函數(shù)調(diào)用中方式相同

例如:

// 泛型類
class Box<Type>{
  contents: Type
  constructor(value: Type){
    this.contents = value
  }
}


// 實例化
const b = new Box("hello")
// const b: Box<string>
console.log('b',b)

類可以像接口一樣使用通用約束和默認值


7.1 靜態(tài)成員中的類型參數(shù)

例:

// 泛型類
class Box<Type>{
  static defaultValue: Type
  // 錯誤: 靜態(tài)成員不能引用類類型參數(shù)依许。
}

示例中的代碼不合法, 原因在于類型總是會被完全擦除的

在運行時,只有一個Box.defaultValue屬性槽, 這意味著設(shè)置Box<string>.defaultValue(如果可能的話)也會被改變?yōu)?code>Box<number>.defaultValue, 這樣就很不好,

泛型類的static成員 永遠不能引用類的類型參數(shù)


8. 參數(shù)屬性

TypeScript提供了特殊的語法,用于將構(gòu)造函數(shù)參數(shù)轉(zhuǎn)換為相同的名稱和值的類屬性, 這些稱謂參數(shù)屬性

是通過在構(gòu)造函數(shù)參數(shù)前面加上修飾符public,protected, privatereadonly.來創(chuàng)建,

沒有參數(shù)屬性前,如果想讓參數(shù)作為屬性, 需要賦值處理

例如:

class Params {
  x: number;
  y: number;
  // 構(gòu)造函數(shù)
  constructor(x:number,y:number){
    this.x = x;
    this.y = y;
  }
}

// 實例化
const a = new Params(10,20)
console.log(a) // {}


現(xiàn)在通過在參數(shù)前添加修飾符, 參數(shù)直接可以變成屬性

例如:

// 類
class Params {

  constructor(
    public readonly x :number ,
    protected y:number ,
    private z:number
  ){
    // ...
  }
}

const a = new Params(1,2,3)
console.log(a) 
/*
  添加完修飾符,編譯后運行, 瀏覽器打印的a對象上就有x,y,z三個參數(shù)屬性
  {x: 1, y: 2, z: 3}
*/

// 1.x 屬性為公共只讀屬性
console.log(a.x)
// (property) Params.x: number

a.x = 20
// 錯誤:無法分配到 "x" 棺禾,因為它是只讀屬性

// 2. y 屬性為受保護屬性, 實例上無法獲取
console.log(a.y)
// 錯誤:屬性“y”受保護,只能在類“Params”及其子類中訪問 

// 3. z屬性為私有的,只有在當(dāng)前類中可以訪問
console.log(a.z)
// 錯誤:屬性“z”為私有屬性峭跳,只能在類“Params”中訪問膘婶。


10.類的表達式

類的表達式與類聲明非常相似, 唯一真正的區(qū)別是類的表達式不需要定義名稱, 我們可以通過他們最終綁定到的任何標識符來引用它們

例如:

// 類的表達式
const SomeClass = class<Type> {
  content:Type;

  constructor(value:Type){
    this.content = value
  }
}

// 實例化
const m = new SomeClass("hello world")
// const m: SomeClass<string>


11 abstract抽象類和成員

11.1 抽象類和抽象成員

TypeScript中的類, 方法, 和字段可能是抽象的

抽象方法或抽象字段是尚未提供實現(xiàn)的方法, 有點像重載,只定義解構(gòu)類型,

這些抽象成員必須存在于抽象類中, 不能直接實例化

抽象類的作用是作為實現(xiàn)所有抽象成員的子類的基類, 但一個類沒有任何抽象成員時, 就說他是具體的類

例子:

// 抽象類
abstract class Base{
  // 抽象方法
  abstract getName():string

  printName(){
    console.log('Hello '+ this.getName())
  }
}

// 實例化抽象類
const b = new Base()
// 報錯: 無法創(chuàng)建抽象類的實例

我們無法實例化Base, 因為他是抽象類, 相反,我們需要創(chuàng)建一個派生類并實現(xiàn)抽象成員

// 抽象類
abstract class Base{
    name: string
    constructor(name:string){
        this.name = name
    }
    // 抽象方法
    abstract getName():string

    printName(){
        console.log('Hello '+ this.getName())
    }
}


// 派生類: 實現(xiàn)抽象成員
class Student extends Base{
    age: number
    constructor(name:string,age:number){
        super(name)
        this.age = age
    }

    // 抽象成員的實現(xiàn)
    getName(): string {
        return this.name
    }
}

// 實例化派生類(實現(xiàn)類)
const b = new Student('小明',18)
b.printName()



請注意, 如果我們忘記實現(xiàn)基類(抽象類)的抽象成員, 我們會得到一個錯誤

// 派生類: 實現(xiàn)抽象成員
class Student extends Base{
  // 錯誤: 非抽象類“Derived”不會實現(xiàn)繼承自“Base”類的抽象成員“getName”。
}

抽象類做為其他派生的基類使用,他們一般不會直接被實例化,不同于接口,抽象類可以包含成員的實現(xiàn)細節(jié)
abstract 關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法


11.2. 抽象構(gòu)造簽名

有時你想接受一些類構(gòu)造函數(shù), 它產(chǎn)生一個派生自某個抽象類的類的實例

例如:

// 抽象類
abstract class Base{
  // 抽象方法
  abstract getName():string

  printName(){
    console.log('Hello '+ this.getName())
  }
}

function greet(ctor: typeof Base){
  const instance = new ctor()

  // 報錯: 無法創(chuàng)建抽象類的實例蛀醉。

  instance.printName()
}

Typescript 正確的告訴您, 您正在嘗試實例化一個抽象類,


相反, 你想編寫一個接受帶有構(gòu)造函數(shù)簽名函數(shù)

function greet(ctor: new () => Base){
  const instance = new ctor()
  instance.printName()
}

greet(Derived)
greet(Base)
/*
  報錯: 
  類型“typeof Base”的參數(shù)不能賦給類型“new () => Base”的參數(shù)悬襟。
  無法將抽象構(gòu)造函數(shù)類型分配給非抽象構(gòu)造函數(shù)類型。
*/

現(xiàn)在 TypeScript 正確地告訴您可以調(diào)用哪些類構(gòu)造函數(shù) -Derived可以拯刁,因為它是具體的脊岳,但Base不能。


12. 類之間的關(guān)系

在大多數(shù)情況下, TypeScript中的類在結(jié)構(gòu)上進行比較, 與其他類型相同

例如, 如下兩個類可以相互替代使用, 因為他們是相同的

// 類1
class Point1{
  x = 0;
  y = 0;
}

// 類2
class Point2{
  x = 0;
  y = 0;
}

// OK
const p:Point1  = new Point2()

因為兩個類相同, 所有可以將Point2類的實例化對象賦值給使用Point1類作為類型注釋的變量


同樣, 即使沒有顯示繼承, 類之間的子類型關(guān)系也是存在

// 隱式的子類
class Person {
  name:string;
  age:number
}

// 隱式的父類
class Employee{
  name: string;
  age: number;
  salary: number;
}

// ok
const p:Person = new Employee()

父類的實例化對象可以賦值給使用子類作為類型注釋的變量,

但是需要注意, 反過來就不可以,因為子類的實例化可能不滿足父類中的成員


空類沒有成員, 在結(jié)構(gòu)類型系統(tǒng)中, 沒有成員的類型通常是其他任何東西的超類型.

所以如果你寫了一個空類(盡量不要), 那么任何東西都可以代替它

// 空類
class Empty{}

function fn(x:Empty){
  console.log(x)
}

// 以前全部ok
fn(window)
fn({})
fn(fn)


13. 類的其他用法

類除了可是實現(xiàn)接口外, 接口也可以反過來擴展類,

簡單說就是可以把類當(dāng)作接口使用

例如:

// 把類當(dāng)成接口使用(相當(dāng)于一個接口)
class Person{
  name:string
  age: number
}

interface Student extends Person{
  sex: string
}

let xiaoming:Student = {name:"小明", age: 18, sex: "男"}
console.log('xiaoming', xiaoming)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垛玻,一起剝皮案震驚了整個濱河市割捅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帚桩,老刑警劉巖亿驾,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異账嚎,居然都是意外死亡莫瞬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門醉锄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乏悄,“玉大人,你說我怎么就攤上這事恳不。” “怎么了开呐?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵烟勋,是天一觀的道長。 經(jīng)常有香客問我筐付,道長卵惦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任瓦戚,我火速辦了婚禮沮尿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己畜疾,他們只是感情好赴邻,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啡捶,像睡著了一般姥敛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瞎暑,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天彤敛,我揣著相機與錄音,去河邊找鬼了赌。 笑死墨榄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的勿她。 我是一名探鬼主播渠概,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嫂拴!你這毒婦竟也來了播揪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筒狠,失蹤者是張志新(化名)和其女友劉穎猪狈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辩恼,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡雇庙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灶伊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疆前。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聘萨,靈堂內(nèi)的尸體忽然破棺而出竹椒,到底是詐尸還是另有隱情,我是刑警寧澤米辐,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布胸完,位于F島的核電站,受9級特大地震影響翘贮,放射性物質(zhì)發(fā)生泄漏赊窥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一狸页、第九天 我趴在偏房一處隱蔽的房頂上張望锨能。 院中可真熱鬧,春花似錦、人聲如沸址遇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傲隶。三九已至饺律,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跺株,已是汗流浹背复濒。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乒省,地道東北人巧颈。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像袖扛,于是被迫代替她去往敵國和親砸泛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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