對於習慣使用其他具有靜態型別語言(例如 C# 和 Java)的程式設計師而言,TypeScript 是熱門選擇。
TypeScript 的類型系統提供許多相同的好處,例如更好的程式碼完成、更早偵測錯誤,以及程式中各部分之間更清晰的溝通。儘管 TypeScript 為這些開發人員提供了許多熟悉的功能,但值得退一步來看看 JavaScript(因此 TypeScript)與傳統的 OOP 語言有何不同。了解這些差異將有助於你撰寫更好的 JavaScript 程式碼,並避免從 C#/Java 直接轉到 TypeScript 的程式設計師可能會遇到的常見陷阱。
共同學習 JavaScript
如果你已經熟悉 JavaScript,但主要是 Java 或 C# 程式設計師,這個入門頁面可以幫助解釋你可能容易遇到的常見誤解和陷阱。TypeScript 建模類型的某些方式與 Java 或 C# 非常不同,在學習 TypeScript 時請務必記住這些方式。
如果您是 Java 或 C# 程式設計師,對於 JavaScript 來說是新手,我們建議先學習一點沒有類型的 JavaScript,以了解 JavaScript 的執行時期行為。因為 TypeScript 沒有改變程式碼的執行方式,您仍然必須學習 JavaScript 的運作方式,才能撰寫實際上會執行某些動作的程式碼!
重要的是要記住,TypeScript 使用與 JavaScript 相同的執行時期,因此任何關於如何達成特定執行時期行為的資源(將字串轉換為數字、顯示警示、將檔案寫入磁碟等)都將永遠同樣適用於 TypeScript 程式。不要將自己侷限於 TypeScript 特有的資源!
重新思考類別
C# 和 Java 是我們所謂的強制 OOP語言。在這些語言中,類別是程式碼組織的基本單位,也是執行時期所有資料和行為的基本容器。強制所有功能和資料都保存在類別中,對於某些問題來說可能是良好的網域模型,但並非每個網域都需要以這種方式表示。
自由函數和資料
在 JavaScript 中,函數可以在任何地方執行,資料可以在沒有預先定義的 class
或 struct
的情況下自由傳遞。這種靈活性非常強大。在沒有隱含 OOP 層級架構的情況下,處理資料的「自由」函數(那些與類別無關的函數)往往是使用 JavaScript 編寫程式時的首選模型。
靜態類別
此外,TypeScript 中不需要某些來自 C# 和 Java 的建構,例如單例和靜態類別。
TypeScript 中的 OOP
話雖如此,您仍然可以使用類別(如果您喜歡的話)!某些問題很適合透過傳統 OOP 層級架構來解決,而 TypeScript 對 JavaScript 類別的支援將使這些模型更加強大。TypeScript 支援許多常見模式,例如實作介面、繼承和靜態方法。
我們稍後會在指南中介紹類別。
重新思考類型
TypeScript 對類型的理解實際上與 C# 或 Java 非常不同。讓我們探討一些差異。
名義具象化類型系統
在 C# 或 Java 中,任何給定的值或物件都有一個確切的類型 - null
、基元或已知的類別類型。我們可以呼叫像 value.GetType()
或 value.getClass()
之類的方法來查詢執行階段的確切類型。此類型的定義將存在於某個名稱的類別中,而且我們無法使用兩個具有類似形狀的類別來代替彼此,除非有明確的繼承關係或共同實作的介面。
這些面向描述了具象化、名義類型系統。我們在程式碼中寫入的類型會存在於執行階段,而且這些類型是透過其宣告而非結構來關聯的。
類型作為集合
在 C# 或 Java 中,將執行時期類型與其編譯時期宣告之間的一對一對應關係視為有意義的。
在 TypeScript 中,最好將類型視為值集合,這些值共享某些共同點。由於類型只是集合,因此特定值可以同時屬於多個集合。
一旦你開始將類型視為集合,某些運算就會變得非常自然。例如,在 C# 中,傳遞一個同時是 string
或 int
的值很尷尬,因為沒有單一類型表示這種值。
在 TypeScript 中,一旦你了解到每個類型只是一個集合,這就會變得非常自然。你如何描述一個值,它可以屬於 string
集合或 number
集合?它僅屬於這些集合的聯集:string | number
。
TypeScript 提供了許多機制以集合論的方式處理類型,如果你將類型視為集合,你會發現它們更直觀。
已抹除的結構類型
在 TypeScript 中,物件不是單一確切類型。例如,如果我們建構一個符合介面的物件,我們可以在預期該介面的地方使用該物件,即使這兩個介面之間沒有宣告關係。
tsTry
interfacePointlike {x : number;y : number;}interfaceNamed {name : string;}functionlogPoint (point :Pointlike ) {console .log ("x = " +point .x + ", y = " +point .y );}functionlogName (x :Named ) {console .log ("Hello, " +x .name );}constobj = {x : 0,y : 0,name : "Origin",};logPoint (obj );logName (obj );
TypeScript 的類型系統是結構性的,而非名義性的:我們可以使用 obj
作為 Pointlike
,因為它有 x
和 y
屬性,且這兩個屬性都是數字。類型之間的關係是由它們包含的屬性決定的,而不是是否以特定關係宣告。
TypeScript 的類型系統也未具體化:在執行階段沒有任何東西會告訴我們 obj
是 Pointlike
。事實上, Pointlike
類型在任何形式下都不會出現在執行階段。
回到類型作為集合的概念,我們可以將 obj
視為 Pointlike
值集合和 Named
值集合的成員。
結構化類型化的後果
OOP 程式設計師常常會對結構化類型化的兩個特定面向感到驚訝。
空類型
第一個是空類型似乎違背了預期
tsTry
classEmpty {}functionfn (arg :Empty ) {// do something?}// No error, but this isn't an 'Empty' ?fn ({k : 10 });
TypeScript 透過查看提供的引數是否為有效的 Empty
來判斷這裡對 fn
的呼叫是否有效。它透過檢查 { k: 10 }
和 class Empty { }
的結構來執行此操作。我們可以看到 { k: 10 }
具有 Empty
所具有的所有屬性,因為 Empty
沒有任何屬性。因此,這是一個有效的呼叫!
這可能令人驚訝,但它最終與名義 OOP 語言中強制執行的關係非常相似。子類別無法移除其基底類別的屬性,因為這樣做會破壞衍生類別與其基底類別之間的自然子類型關係。結構類型系統僅透過根據具有相容類型屬性的方式描述子類型來隱式識別此關係。
相同的類型
另一種常見的驚喜來自於相同類型
ts
class Car {drive() {// hit the gas}}class Golfer {drive() {// hit the ball far}}// No error?let w: Car = new Golfer();
同樣地,這不是錯誤,因為這些類別的結構是相同的。雖然這看起來像是潛在的混淆來源,但實際上,不應相關的相同類別並不常見。
我們將在類別章節中深入了解類別如何彼此關聯。
反射
OOP 程式設計師習慣於查詢任何值的類型,即使是泛型
csharp
// C#static void LogType<T>() {Console.WriteLine(typeof(T).Name);}
由於 TypeScript 的類型系統已完全消除,因此在執行階段無法取得關於泛型類型參數實例化等資訊。
JavaScript 確實有一些受限的基本類型,例如 typeof
和 instanceof
,但請記住,這些運算子仍作用於類型消除輸出程式碼中存在的數值。例如,typeof (new Car())
將會是 "object"
,而不是 Car
或 "Car"
。
後續步驟
這是日常 TypeScript 中所使用的語法和工具的簡要概述。從這裡,您可以
- 閱讀完整的指南 從頭到尾
- 探索 Playground 範例