JavaScript => TypeScript 類入門

嘗試重寫

在此以前,經過《JavaScript => TypeScript 入門》已經掌握了類型聲明的寫法。原覺得憑着那一條無往不利的規則,就能夠開開心心的重寫 JS 項目了。當我躍躍欲試去重寫一個 JS 項目時,發現阻礙重重。編程

在投靠 TS 時,TypeScript 容許將 JS 漸進式的過渡到 TS:segmentfault

  • 一些簡單的基本類型聲明的填充,好比:number, :string, :string[] 等等;安全

  • 將一些稍複雜的、暫時還未提煉好的結構聲明爲 any 類型;數據結構

  • 改後綴爲 .ts, 執行 tsc 編譯。編程語言

但一個明顯的問題是,一個 JS 文件中每每有不少模塊依賴,意味着要把全部這些模塊都從新作一次類型聲明,才能總體編譯經過。函數

因此,要想暢通無阻的重寫 JS,在上一篇入門的基礎上,還得強忍着衝動,繼續學習兩部份內容:工具

一、TS 類的寫法、特徵
二、TS 模塊、命名空間學習

好在,它們和 ES6 其實有着不少重合的地方——要記得,TS 是 ES6 的超集。因此若是掌握了 ES6,咱們只要挑差別,找增量,進行遷移學習,就能快速掌握上述內容。this

ES6 的類

ES6 的類的概念,和廣泛編程語言的類概念一致,表示一種特有的數據結構。它既像一個函數(能被 new 調用),又像一個對象(包含了各類屬性、方法)。code

class MyClass {
  constructor() {
    this.attr = 'my attribute';
  }
  foo(name) {
    console.log('hello ' +  name);
  }
}
let myclass = new MyClass();

類中的方法屬性被稱爲 「成員」,這些成員大概被分紅 3 種類別:

  • 公有屬性、公有方法

  • 私有屬性、私有方法

  • 靜態屬性、靜態方法

在寫繼承時,咱們並不但願在全部時候,這些屬性、方法都被繼承到子類;在訪問成員時,也不但願在任什麼時候候,類實例的全部成員都無一例外能夠被訪問;有時候咱們但願與此相反,這樣能保持開放出去的信息簡潔乾淨。

JS 很早就有這些概念,但到如今爲止,ES6 並無處理好這個事情。

私有屬性、私有方法

ES6 沒有直觀的表示私有屬性、私有方法的方案,只能經過變通的方式間接地表示。

const getKeys = Symbol('getKeys_');
const attr = Symbol('attr_');
class MyClass {
  constructor() {
    // 私有屬性
    this[attr] = 'private attribute';
  }
  // 公有方法
  foo(config) {
    return this[fn](config);
  }
  // 私有方法
  [getKeys](config) {
    return Object.keys(config);
  }
};

這僅僅是一個間接取巧的方式避免直接被訪問到,但很輕易就能繞過它:

let myclass = new MyClass();

let symbolsAttr = Object.getOwnPropertySymbols(myclass);
let attr_ = myclass[symbolsAttr[0]]; // 即訪問到私有屬性

let symbolsFn = Object.getOwnPropertySymbols(myclass.__proto__);
let getKeys_ = myclass[symbolsFn[0]]; // 即訪問到私有方法

私有方法、私有屬性的目的是不想開放過多的信息到外部。上面變通的方案雖然表面上達到了目的,可是不夠直觀,也不安全。

靜態屬性、靜態方法

ES6 目前支持靜態方法表示,類屬性及靜態屬性目前做爲提案還未正式成爲標準。

class MyClass {
  name = 'jeremy';  // ES6 不被支持,僅做爲提案
  static version = '1.0.0'; // ES6 不被支持,僅做爲提案
  constructor() {
    console.log(MyClass.version); // '1.0.0'
  }
  static get Version() {
    return MyClass.version;
  }
}

區分這些成員身份,與動態類型、靜態類型語言沒有必然聯繫,能夠預見,在不久的未來,ES6 這方面將獲得完善。

TS 的類

鑑於 TS 是 ES6 的超集這一事實,TS 類固然也有私有屬性、私有方法靜態屬性、靜態方法等這些身份的成員。在標記成員身份上,TS 與 Java 有不少類似之處。好比與繼承相關的訪問修飾符,以及其餘限定做用的非訪問修飾符

訪問修飾符

  • private

  • public

  • protected

非訪問修飾符

  • static

  • readonly

  • abstract

class User {
    readonly name: string;
    public age: number;
    private sex: string;
    protected marriage: string;
    constructor(name: string, sex: string) { 
        this.name = name;
        this.sex = sex;
    }
    showAge() {
        console.log(`${this.name}, age ${this.age}`);
    }
}

訪問修飾符

一、當成員被標記成 private 時,它就不能在聲明它的類的外部訪問。
二、protected 修飾符與 private 修飾符的行爲很類似,但有一點不一樣,protected 成員在派生類中仍然能夠訪問
三、被聲明爲 public 的類、方法、構造方法和接口可以被任何其餘類訪問。

class User {
    readonly name: string;
    public age: number;
    private sex: string;
    protected marriage: string;
    constructor(name: string, sex: string) { 
        this.name = name;
        this.sex = sex;
    }
    showAge() {
        console.log(this.age);
    }
}

let a = new User('jerry', 'male');
console.log(a.sex); // 編譯報錯:私有屬性不準在本類以外被訪問
console.log(a.marriage); // 編譯報錯:私有屬性不準在類以外被訪問
a.name = 'jeremy'; // 編譯報錯:只讀屬性不準再次被寫入

訪問修飾符主要用在繼承的可訪問性上,繼承比如遺傳,用基因類比理解它們就頗有意思:

  • private 私有基因——本體有效,不會被繼承,即子類中沒法訪問到

  • protected 被保護基因——保護血統純正,只容許在繼承體系中訪問

  • public 公共基因——子類、其餘類都能訪問到

有了訪問修飾符,每一個成員都有扮演着與身份對應的角色,真正作到名副其實。

訪問修飾符和非訪問修飾符加起來有6個甚至更多,它們如何與類、接口一塊兒搭配工做,考慮到組合狀況很是多,此處不去細緻的探究。能夠參考 Java 的規則:

default (即缺省,什麼也不寫): 在同一包(等同於JS中的模塊)內可見,不使用任何修飾符。使用對象:類、接口、變量、方法。

private : 在同一類內可見。使用對象:變量、方法。 注意:不能修飾類(外部類)

public : 對全部類可見。使用對象:類、接口、變量、方法

protected : 對同一包內的類和全部子類可見。使用對象:變量、方法。 注意:不能修飾類(外部類)。

固然更直觀的方式是上代碼 + 編譯。TS 的編譯工具備着很是友好、明確編譯錯誤提示。

class Demo {
    static private showSelf() {}
}
// 編譯錯誤提示
error TS1029: 'private' modifier must precede 'static' modifier.

固然,去查閱官方文檔也是一個好辦法。

非訪問修飾符

非訪問修飾符不關心可訪問性,被它單獨標記的成員,在任什麼時候候都能訪問到。目前至少有這些:

  • static

  • get

  • set

  • readonly

  • abstract

從字面上看,不難理解它們的用途。這裏和 ES6 差異較大的是 abstract

abstract 修飾符

abstract 修飾符用來定義抽象類和在抽象類內部定義抽象方法。

在 Java 中,抽象類不能用來實例化對象,主要作爲其它派生類的基類使用。 不一樣於接口,抽象類能夠包含成員的實現細節。

TS 中也是這樣規定的:抽象類不容許直接被實例化。

還有一點很重要,抽象類中的抽象方法能夠不包含具體實現,但必須在派生類中實現。

// 抽象類
abstract class User {
    readonly name: string;
    public age: number;
    private sex: string;
    protected marriage: string;
    constructor(name: string, sex: string) { 
        this.name = name;
        this.sex = sex;
    }
    showAge() {
        console.log(this.age);
    }
    // 抽象方法
    abstract showName(): void;
}

// let a = new User('jerry', 'male'); // 編譯報錯: 抽象類不容許直接實例化

容易忽略的一個問題是,一個類中一旦出現抽象方法,那這個類整個應該被標記爲 abstract

class UserA extends User {
    constructor(name: string, sex: string) {
        super(name, sex);
        // console.log(this.sex); // 編譯報錯:私有變量不能被訪問
    }
    // 抽象方法必需要在子類中實現
    // 若無此方法,編譯報錯
    showName() {
       console.log(this.name); 
    }
}

基本上,對 TS 類只須要掌握這些,深刻的部分天然在實踐中繼續領悟。

對了,還有模塊、命名空間沒有梳理。由於碼文字的麻煩,只得留到後續再補。可是直接搬用 ES6 的模塊import, export 就徹底夠用了。

So,這回真的去重寫了。

相關文章
相關標籤/搜索