從 JavaScript 到 TypeScript - 聲明類型

2018-08-28 20:34 更新

從 JavaScript 語法改寫為 TypeScript 語法,有兩個(gè)關(guān)鍵點(diǎn),一點(diǎn)是類成員變量(Field)需要聲明,另一點(diǎn)是要為各種東西(變量、參數(shù)、函數(shù)/方法等)聲明類型。而這兩個(gè)點(diǎn)直接引出了兩個(gè)關(guān)鍵性的問題,有哪些類型?怎樣聲明?

類型

在說 TypeScript 的類型之前,我們先復(fù)習(xí)一下 JavaScript 的七種類型:

  • undefined
  • function
  • boolean
  • number
  • string
  • object
  • symbol

    這七種類型都是可以通過 typeof 運(yùn)算符算出來的,但其中并沒有我們常見的 ArraynullDate 之類的類型——因?yàn)樗鼈兤鋵?shí)都是 object。

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

  • Array<T>,或 T[],表示 T 類型的數(shù)組
  • null,空類型,其作用與 strictNullChecks 編譯參數(shù)有關(guān)
  • Tuple(元組),形如 [Number, String]
  • enum T,定義枚舉類型 T,可理解為集中對(duì)數(shù)值常量進(jìn)行命名
  • interface T,接口,T 是一種接口類型
  • class T,類,T 是一種類型
  • any,代表任意類型
  • void,表示沒有類型,用于聲明函數(shù)類型
  • never,表示函數(shù)不可返回的神奇類型
  • ……

具體的類型這里就不詳述了,官方 Handbook 的 Basic Type、InterfacesClasses、Enum、Advanced Types 這幾部分說得非常清楚。

不過仍然有一種類型相關(guān)的特性不得不提——泛型。如果只是說數(shù)據(jù)類型,純粹的 JSer 們還可以理解,畢竟類型不是新鮮玩意兒,只是擴(kuò)展了點(diǎn)種類。但是泛型這個(gè)東西,純粹的 JSer 們可能就沒啥概念了。

泛型主要是用一個(gè)符號(hào)來表示一些類型,只要是符合約束條件(默認(rèn)無約束)的類型,都可以替換掉這個(gè)類型符號(hào)來使用,比如

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


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

泛型與強(qiáng)類型相關(guān),即需要進(jìn)行嚴(yán)格的類型檢查,又想少寫相似代碼,所以干脆用某個(gè)符號(hào)來代替類型。泛型這個(gè)名稱本身可能并不是很好理解,但是如果借用 C++ 的“模板”概念,就好理解了。比如上面的泛型函數(shù),根據(jù)后面的調(diào)用,可以被解釋為三個(gè)函數(shù),相當(dāng)于套用模板,用實(shí)際類型代替了 T

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

關(guān)于泛型,更詳細(xì)的內(nèi)容可以參考 Handbook 的 Generic 部分。

類型就簡述到這里,簡單的類型一看就能明白,高級(jí)一點(diǎn)的類型我們以后再開專題來詳述。不過既然選擇使用 TypeScript,必然會(huì)用到它的靜態(tài)類型特性,那就必須強(qiáng)化識(shí)別類型的意識(shí),并養(yǎng)成這樣的習(xí)慣。對(duì)于純 JSer 來說,這是一個(gè)巨大的挑戰(zhàn)。

聲明類型

聲明類型,主要是指聲明變量/常量,函數(shù)/方法和類成員的類型。JS 中使用 var 聲明一個(gè)變量,ES6 擴(kuò)展了 let 和 const。這幾種聲明 TypeScript 都支持。要為變量或者常量指定類型也很簡單,就是在變量/常量名后面加個(gè)冒號(hào),再指定類型即可,比如

// # typescript


// 聲明函數(shù) pow 是 number 類型,即返回值是 number 類型
// 聲明參數(shù) 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));
    }
}

這段代碼演示了對(duì)函數(shù)類型、參數(shù)類型和變量類型地聲明。這相對(duì)于 JavaScript 代碼來說,似乎變得更復(fù)雜了。但是考慮下,如果我們?cè)谀程幉恍⌒倪@樣調(diào)用了 pow

// # javascript


let n = "a";
let r = pow(n);     // 這里存在一個(gè)潛在的錯(cuò)誤

JavaScript 不會(huì)提前檢查錯(cuò)誤的,只有在執(zhí)行到 r = pow(n) 的時(shí)候給 r 賦值為 NaN。然后如果別處又用到 r,可能就會(huì)造成連鎖錯(cuò)誤,可能很要調(diào)試一陣才把問題找得出來。

不過上面兩行代碼在 TypeScript 里是通不過轉(zhuǎn)譯的,它會(huì)報(bào)告一個(gè)類型不匹配的錯(cuò)誤:

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

聲明類成員

這時(shí)先來看一段 JavaScript 代碼

// # javascript (es6)


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


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

這段 JavaScript 代碼如果翻譯成 TypeScript 代碼,會(huì)是這樣

// # 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 里是不需要聲明的,對(duì) this._name 賦值,它自然就有了,但在 TypeScript 里如果不聲明,就會(huì)報(bào)告屬性不存在的錯(cuò)誤:

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

雖然寫起來麻煩了一點(diǎn),但是我也能理解 TypeScript 的苦衷。如果沒有這些聲明,tsc 就搞不清楚你在使用 obj.xxxx 或者 this.xxxx 的時(shí)候,這個(gè) xxxx 到底確實(shí)是你想要添加的屬性名稱呢,還是你不小心寫錯(cuò)了的呢?

另外要注意到的是 privatepublic 修飾符。JavaScript 中存在私有成員,為了實(shí)現(xiàn)私有,大家都想了不少辦法,比如閉包。

TypeScript 提供了 private 來修飾私有成員,protected 修改保護(hù)(子類可用)成員,public 修飾公共成員。如果不添加修飾符,默認(rèn)作為 public,以兼容 JavaScript 的類成員定義。不過特別需要注意的是,這些修飾符只在 TypeScript 環(huán)境(比如轉(zhuǎn)譯過程)有效,轉(zhuǎn)譯成 JavaScript 之后,仍然所有成員都是公共訪問權(quán)限的。比如上例中的 TypeScript 代碼轉(zhuǎn)譯出來基本上就是之前的 JavaScript 代碼,其 _name 屬性在外部仍可訪問。

當(dāng)然在 TypeScript 代碼中,如果外部訪問了 _name,tsc 是會(huì)報(bào)告錯(cuò)誤的

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

所以應(yīng)用內(nèi)使用 private 完全沒問題,但是如果你寫的東西需要做為第三方庫發(fā)布,那就要想一些手段來進(jìn)行“私有化”了,其手段和 JavaScript 并沒什么不同。

小結(jié)

從 JavaScript 語法改寫 TypeScript 語法,我們來做個(gè)簡單的總結(jié):

  1. 類成員需要聲明。
  2. 變量、函數(shù)參數(shù)和返回值需要申明類型。

如果所有這些東西都要聲明類型,工作量還是滿大的,所以我建議:就接口部分聲明類型。也就是說,類成員、函數(shù)/方法的參數(shù)和返回類型要聲明類型,便于編輯器進(jìn)行語法提示,局部使用的變量或者箭頭函數(shù),在能明確推導(dǎo)出其類型的時(shí)候,可以不聲明類型。

擴(kuò)展閱讀

此文首發(fā)于 SegmentFault

敬請(qǐng) 掃碼 關(guān)注〔邊城〕的公眾號(hào):邊城客棧

公眾號(hào)“邊城客?!? /></p></div>
				          <div style=

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)