首先咱們要清楚 private
、 protected
現階段只是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
則默認是protected
。java
可是,當比較具備 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
在判斷類型兼容時,爲何處理 private
、protected
的規則要有別於 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
是結構式語言,因Tesla
、Nio
又有着相同名稱、類型的字段 maxMileage
,即便 drive
入參聲明爲 Tesla
類型,也能經過校驗。目前而言,即便誤用,drive
的表現同樣,不會有問題,但隨着技術的發展,兩個品牌的 maxMileage
值將不同,drive
的行爲也將千差萬別。這個bug將一直潛伏着,直到引發嚴重故障纔會引發關注。繼承
在上例基礎上增長1) 2) 兩處,多了 private
(protected
亦可) 聲明的 brand
屬性,來解決結構同樣,但又想區分類型的場景,達到相似聲明式類型系統的效果。這裏就是利用了private
、protected
屬性必須源於同一處聲明纔可斷定類型兼容。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
語義上是私有的,對子類不可見,因此不能進行覆蓋,而protected
、public
語義上就是對子類可見的,子類知道當前在進行覆蓋行爲,這只是一方面。
咱們假設 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
類聲明繼承時的報錯信息和前面的同樣。