TypeScript中的private、protected


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

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

TypeScript是一個結構類型語言。當比較兩個不同的類型時,不管它們來自哪里,如果所有成員的類型都是兼容的,那么就說這些類型本身是兼容的。

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

但是,當比較具有 private 成員或 protected 成員的類型時,會區別對待這些類型。如果其中一種類型具有private成員,那么另一種類型必須具有來源於同一處聲明的private成員。這同樣適用於protected成員。

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,這里只是做個簡要的引子。

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

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

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屬性必須源於同一處聲明才可判定類型兼容。

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 屬性,增加了運行時開銷,如果這不是你想要的,可以如下處理:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM