TypeScript中的private、protected

首先咱們要清楚 privateprotected 現階段只是javascript中的保留字(Reserved words),而非關鍵字(Keywords )。所以TypeScript中的純類型聲明語句,編譯後都會被擦除。javascript

class Person {
  public name: string;
  protected age: number; 
  private isMarried: boolean;
}
//編譯結果
class Person {
}

TypeScript是一個結構類型語言。當比較兩個不一樣的類型時,無論它們來自哪裏,若是全部成員的類型都是兼容的,那麼就說這些類型自己是兼容的。html

interface Named {
  name: string;
}

class Bar {
  name: string;
}

class Foo {
  name: string;
}

// OK, because of structural typing
let a: Named = new Person(); //✔️
let b: Foo = new Bar(); //✔️

因爲 TypeScript 屬性聲明默認是 public,因此上面能夠以 b.name 形式訪問,而java則默認是protectedjava

可是,當比較具備 private 成員或 protected 成員的類型時,會區別對待這些類型。若是其中一種類型具備private成員,那麼另外一種類型必須具備來源於同一處聲明的private成員。這一樣適用於protected成員。typescript

class Bar {
  private name: string;
}

class Foo {
  private name: string;
}

let bar: Bar = new Foo(); // ❌ 
//Type 'Foo' is not assignable to type 'Bar'.
  //Types have separate declarations of a private property 'name'.

上面的這些概念規則來源於 TypeScript Handbook,這裏只是作個簡要的引子。this

TypeScript 在判斷類型兼容時,爲何處理 privateprotected 的規則要有別於 public , 這究竟有什麼潛在的好處。code

假設有這樣一個場景,目前電動汽車尚且處於發展的初級階段,汽車品牌特斯拉、蔚來的最大里程數 maxMileage 值同樣。htm

interface Car {
  maxMileage: number;
}

class Tesla implements Car {
   maxMileage: number = 500;
}

class Nio implements Car {
   maxMileage: number = 500;
}

function drive(car :Tesla) {
   console.log(car.maxMileage)
}

let tesla = new Tesla();
let nio = new Nio();
drive(tesla); // ✔️
drive(nio); // ✔️

因爲TypeScript是結構式語言,因TeslaNio又有着相同名稱、類型的字段 maxMileage ,即便 drive 入參聲明爲 Tesla 類型,也能經過校驗。目前而言,即便誤用,drive 的表現同樣,不會有問題,但隨着技術的發展,兩個品牌的 maxMileage 值將不同,drive 的行爲也將千差萬別。這個bug將一直潛伏着,直到引發嚴重故障纔會引發關注。繼承

在上例基礎上增長1) 2) 兩處,多了 private(protected亦可) 聲明的 brand 屬性,來解決結構同樣,但又想區分類型的場景,達到相似聲明式類型系統的效果。這裏就是利用了privateprotected屬性必須源於同一處聲明纔可斷定類型兼容。ip

class Tesla implements Car {
   private brand: string = "Tesla"; // 1)
   maxMileage: number = 500;
}

class Nio implements Car {
   private brand: string = "Tesla";  //2)
   maxMileage: number = 500;
}

function drive(car :Tesla) {
   console.log(car.maxMileage)
}
let tesla = new Tesla();
let nio = new Nio();
drive(tesla); // ✔️
drive(nio); // ❌
//Argument of type 'Nio' is not assignable to parameter of type 'Tesla'.
  //Types have separate declarations of a private property 'brand'.

//編譯後
class Tesla {
    constructor() {
        this.brand = "Tesla";
        this.maxMileage = 500;
    }
}
class Nio {
    constructor() {
        this.brand = "Tesla";
        this.maxMileage = 500;
    }
}

雖然達到了咱們想要的效果,但類實例會多出 brand 屬性,增長了運行時開銷,若是這不是你想要的,能夠以下處理:ci

class Tesla implements Car {
  //@ts-ignore
   private brand: string;
   maxMileage: number = 500;
}

class Nio implements Car {
   //@ts-ignore
   private brand: string ;
   maxMileage: number = 500;
}

//編譯後
class Tesla {
    constructor() {
        this.maxMileage = 500;
    }
}
class Nio {
    constructor() {
        this.maxMileage = 500;
    }
}

能夠看到編譯後的代碼很純淨了。//@ts-ignore僅在 strictPropertyInitialization: true 時須要,避免因未初始化屬性而編譯報錯。

Types have separate declarations of a private property 報錯還會出如今類extends繼承的時候。初看很奇怪,使用姿式不一樣,但報錯信息且相似。

class ElectricVehicle {
   private charge() {};
}

//Type 'FF91' is not assignable to type 'ElectricVehicle'.
 // Types have separate declarations of a private property 'charge'
class FF91 extends ElectricVehicle {   // ❌
    private charge() {};
}

經過將 private 改爲 protected或public 能夠修復。不少文章會提到這是因爲 private 語義上是私有的,對子類不可見,因此不能進行覆蓋,而protectedpublic 語義上就是對子類可見的,子類知道當前在進行覆蓋行爲,這只是一方面。

咱們假設 TypeScript 容許覆蓋 private 方法,上面的類聲明編譯經過。但當咱們執行下面語句時,上面的報錯再次出現。

let parent = new ElectricVehicle();
let child = new FF91();
parent = child; // ❌
//Type 'FF91' is not assignable to type 'ElectricVehicle'.
 // Types have separate declarations of a private property 'charge'

最初的示例,Foo、Bar 只是兩個結構相似的類,並沒有繼承關係,斷定類型不兼容尚可理解。這裏父子類之間類型不兼容就無法自圓了。
因此編譯器提早在類聲明時就報錯,避免延後到使用階段。這也是爲何 FF91 類聲明繼承時的報錯信息和前面的同樣。

示例 Playground

相關文章
相關標籤/搜索