從 JavaScript 到 TypeScript - 聲明類型

從 JavaScript 語法改寫爲 TypeScript 語法,有兩個關鍵點,一點是類成員變量(Field)須要聲明,另外一點是要爲各類東西(變量、參數、函數/方法等)聲明類型。而這兩個點直接引出了兩個關鍵性的問題,有哪些類型?怎樣聲明?javascript

類型

在說 TypeScript 的類型以前,咱們先複習一下 JavaScript 的七種類型:html

  • undefinedjava

  • functiones6

  • booleantypescript

  • numbersegmentfault

  • string數組

  • object閉包

  • symbol編輯器

這七種類型都是能夠經過 typeof 運算符算出來的,但其中並無咱們常見的 ArraynullDate 之類的類型——由於它們其實都是 object模塊化

TypeScript 的重要特性之一就是類型,因此 TypeScript 中的類型要講究得多,除了 JavaScript 中的類型以外,還定義了其它一些(不徹底列表)

  • Array<T>,或 T[],表示 T 類型的數組

  • null,空類型,其做用與 strictNullChecks 編譯參數有關

  • Tuple(元組),形如 [Number, String]

  • enum T,定義枚舉類型 T,可理解爲集中對數值常量進行命名

  • interface T,接口,T 是一種接口類型

  • class T,類,T 是一種類型

  • any,表明任意類型

  • void,表示沒有類型,用於聲明函數類型

  • never,表示函數不可返回的神奇類型

  • ……

具體的類型這裏就不詳述了,官方 Handbook 的 Basic TypeInterfacesClassesEnumAdvanced Types 這幾部分說得很是清楚。

不過仍然有一種類型相關的特性不得不提——泛型。若是隻是說數據類型,純粹的 JSer 們還能夠理解,畢竟類型不是新鮮玩意兒,只是擴展了點種類。可是泛型這個東西,純粹的 JSer 們可能就沒啥概念了。

泛型主要是用一個符號來表示一些類型,只要是符合約束條件(默認無約束)的類型,均可以替換掉這個類型符號來使用,好比

function test<T>(v: T) {
    console.log(v);
}

test<boolean>(true);    // 顯式指定 T 由 boolean 替代
test("hello");          // 推斷(隱式) T 被 string 替代
test(123);              // 推斷(隱式) T 被 number 替代

泛型與強類型相關,即須要進行嚴格的類型檢查,又想少寫類似代碼,因此乾脆用某個符號來代替類型。泛型這個名稱自己可能並非很好理解,可是若是借用 C++ 的「模板」概念,就好理解了。好比上面的泛型函數,根據後面的調用,能夠被解釋爲三個函數,至關於套用模板,用實際類型代替了 T

function test(v: boolean) { ... }
function test(v: string) { ... }
function test(v: number) { ... }

關於泛型,更詳細的內容能夠參考 Handbook 的 Generic 部分。

類型就簡述到這裏,簡單的類型一看就能明白,高級一點的類型咱們之後再開專題來詳述。不過既然選擇使用 TypeScript,必然會用到它的靜態類型特性,那就必須強化識別類型的意識,並養成這樣的習慣。對於純 JSer 來講,這是一個巨大的挑戰。

聲明類型

聲明類型,主要是指聲明變量/常量,函數/方法和類成員的類型。JS 中使用 var 聲明一個變量,ES6 擴展了 let 和 const。這幾種聲明 TypeScript 都支持。要爲變量或者常量指定類型也很簡單,就是在變量/常量名後面加個冒號,再指定類型便可,好比

// # typescript

// 聲明函數 pow 是 number 類型,即返回值是 number 類型
// 聲明參數 n 是 number 類型
function pow(n: number): number {
    return n * n;
}

// 聲明 test 是無返回值的
function test(): void {
    for (let i: number = 0; i < 10; i++) {  // 聲明 i 是 number
        console.log(pow(i));
    }
}

這段代碼演示了對函數類型、參數類型和變量類型地聲明。這相對於 JavaScript 代碼來講,彷佛變得更復雜了。可是考慮下,若是咱們在某處不當心這樣調用了 pow

// # javascript

let n = "a";
let r = pow(n);     // 這裏存在一個潛在的錯誤

JavaScript 不會提早檢查錯誤的,只有在執行到 r = pow(n) 的時候給 r 賦值爲 NaN。而後若是別處又用到 r,可能就會形成連鎖錯誤,可能很要調試一陣才把問題找得出來。

不過上面兩行代碼在 TypeScript 裏是通不過轉譯的,它會報告一個類型不匹配的錯誤:

Argument of type 'string' is not assignable to parameter of type 'number'.

聲明類成員

這時先來看一段 JavaScript 代碼

// # javascript (es6)

class Person {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name;
    }
}

這段 JavaScript 代碼若是翻譯成 TypeScript 代碼,會是這樣

// # typescript

class Person {
    private _name: string;

    public constructor(name: string) {
        this._name = name;
    }

    public get name(): string {
        return this._name;
    }
}

注意到 private _name: string,這句話是在聲明類成員變量 _name。JavaScript 裏是不須要聲明的,對 this._name 賦值,它天然就有了,但在 TypeScript 裏若是不聲明,就會報告屬性不存在的錯誤:

Property '_name' does not exist on type 'Person'.

雖然寫起來麻煩了一點,可是我也能理解 TypeScript 的苦衷。若是沒有這些聲明,tsc 就搞不清楚你在使用 obj.xxxx 或者 this.xxxx 的時候,這個 xxxx 到底確實是你想要添加的屬性名稱呢,仍是你不當心寫錯了的呢?

另外要注意到的是 privatepublic 修飾符。JavaScript 中存在私有成員,爲了實現私有,你們都想了很多辦法,好比閉包。

TypeScript 提供了 private 來修飾私有成員,protected 修改保護(子類可用)成員,public 修飾公共成員。若是不添加修飾符,默認做爲 public,以兼容 JavaScript 的類成員定義。不過特別須要注意的是,這些修飾符只在 TypeScript 環境(好比轉譯過程)有效,轉譯成 JavaScript 以後,仍然全部成員都是公共訪問權限的。好比上例中的 TypeScript 代碼轉譯出來基本上就是以前的 JavaScript 代碼,其 _name 屬性在外部仍可訪問。

固然在 TypeScript 代碼中,若是外部訪問了 _name,tsc 是會報告錯誤的

Property '_name' is private and only accessible within class 'Person'.

因此應用內使用 private 徹底沒問題,可是若是你寫的東西須要作爲第三方庫發佈,那就要想一些手段來進行「私有化」了,其手段和 JavaScript 並沒什麼不一樣。

小結

從 JavaScript 語法改寫 TypeScript 語法,咱們來作個簡單的總結:

  1. 類成員須要聲明。

  2. 變量、函數參數和返回值須要申明類型。

若是全部這些東西都要聲明類型,工做量仍是滿大的,因此我建議:就接口部分聲明類型。也就是說,類成員、函數/方法的參數和返回類型要聲明類型,便於編輯器進行語法提示,局部使用的變量或者箭頭函數,在能明確推導出其類型的時候,能夠不聲明類型。

擴展閱讀


關注做者的公衆號「邊城客棧」 →

相關文章
相關標籤/搜索