從 JavaScript 語法改寫為 TypeScript 語法,有兩個關鍵點,一點是類別成員變數(Field)需要聲明,另一點是要為各種東西(變數、參數、函數/方法等)聲明類型。
從 ES6 語法改寫為 TypeScript 語法,有兩個比較重要的知識點,一個是宣告類別成員,另一個是宣告類型。這兩個語法特點在 JavaScript 中都不存在。而這兩個點直接引出了兩個關鍵性的問題,有哪些類型?怎樣聲明?
類型
在說TypeScript 的型別之前,我們先複習一下JavaScript 的七種型別:
undefined
function
boolean
number
string
object
symbol
這七種型別都是可以透過typeof 運算子算出來的,但其中並沒有我們常見的Array、null,Date 之類的類型——因為它們其實都是object。
TypeScript 的重要特性之一就是型,所以TypeScript 中的型別要講究得多,除了JavaScript 中的型別之外,還定義了其它一些(不完全列表)
Array
null,空型,其作用與strictNullChecks 編譯參數有關
Tuple(元組),形如[Number, String]
enum T,定義枚舉類型T,可理解為集中對數值常數進行命名
interface T,接口,T 是一種接口類型
class T,類, T 是一種類型
any,代表任意型別
void,表示沒有類型,用於宣告函數型別
never,表示函數不可傳回的神奇型別
……
具體的類型這裡就不詳述了,官方Handbook 的 Basic Type、Interfaces、Classes、Enum、Advanced Types 這幾部分說得非常清楚。
不過仍有一種與類型相關的特性不得不提-泛型。如果只是說資料型,純粹的 JSer 們還可以理解,畢竟類型不是新鮮玩意兒,只是擴展了點種類。但是泛型這個東西,純粹的 JSer 們可能就沒啥概念了。
泛型主要是用一個符號來表示一些類型,只要是符合約束條件(預設無約束)的類型,都可以替換掉這個類型符號來使用,例如
function test
console.log(v);}
test
#泛型與強型別相關,即需要嚴格的型別檢查,又想少寫相似程式碼,所以乾脆用某個符號來代替類型。泛型這個名稱本身可能不是很好理解,但是如果借用 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 到底确实是你想要添加的属性名称呢,还是你不小心写错了的呢?
另外要注意到的是 private 和 public 修饰符。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 语法,我们来做个简单的总结:
类成员需要声明。
变量、函数参数和返回值需要申明类型。
如果所有这些东西都要声明类型,工作量还是满大的,所以我建议:就接口部分声明类型。也就是说,类成员、函数/方法的参数和返回类型要声明类型,便于编辑器进行语法提示,局部使用的变量或者箭头函数,在能明确推导出其类型的时候,可以不声明类型。
以上是介紹JavaScript和TypeScript的聲明類型的詳細內容。更多資訊請關注PHP中文網其他相關文章!