TypeScript - 型別擦除和錯誤行為?
TypeScript 是一種流行的程式語言,它提供了諸如型別檢查和型別註解等特性,以幫助開發人員編寫更健壯、更易於維護的程式碼。但是,當 TypeScript 程式碼編譯成 JavaScript 時,型別資訊會在稱為型別擦除的過程中丟失。這可能導致執行時錯誤,這些錯誤難以診斷和修復。
在本文中,我們將探討 TypeScript 中型別擦除的概念,以及它如何影響我們程式碼中的錯誤行為。
型別擦除
型別擦除是在編譯期間從程式中刪除型別資訊的過程。在 TypeScript 中,這意味著當代碼編譯成 JavaScript 時,我們新增到程式碼中的型別註解會被刪除。這是因為 JavaScript 沒有像 TypeScript 這樣的型別系統,因此在執行時不需要型別資訊。
例如,考慮以下 TypeScript 程式碼:
function add(a: number, b: number): number { return a + b; }
當此程式碼編譯成 JavaScript 時,生成的程式碼如下所示:
function add(a, b) { return a + b; }
請注意,型別註解已被刪除,只留下函式簽名。這意味著在執行時,無法知道a和b的型別,並且傳遞錯誤型別引數所導致的任何錯誤都將在執行時發生。
瀏覽器或執行時環境被構建為執行 JavaScript 程式碼而不是 TypeScript 程式碼。因此,每次我們想要執行 TypeScript 程式碼時,它都會先被轉換為 JavaScript。這個轉換過程包括刪除型別註解,因此在執行時不會進行任何提供的型別檢查。
錯誤行為
當型別資訊被擦除時,原本會被 TypeScript 編譯器捕獲的錯誤可能會改為在執行時發生。這可能使得診斷和修復程式碼中的錯誤變得更加困難。
示例 1
例如,考慮以下 TypeScript 程式碼:
function add(a: number, b: number): number { return a + b; } console.log(`The result of addition operation is: ${add("1", "2")}`);
當此程式碼編譯成 JavaScript 時,型別註解會被刪除,錯誤會被標記到控制檯上。
Argument of type 'string' is not assignable to parameter of type 'number'.
上面程式碼的 JavaScript 程式碼如下所示:
function add(a, b) { return a + b; } console.log(`The result of addition operation is: ${add("1", "2")}`);
輸出
The result of addition operation is: 12
在執行時,console.log語句將輸出"12",這不是我們期望的結果。這是因為我們向add函式傳遞了兩個字串而不是兩個數字,並且由於型別資訊被擦除,該函式能夠執行而不會丟擲錯誤。要解決此問題,我們需要新增執行時檢查以確保傳遞給add的引數實際上是數字。
與其他傳統的編譯器(GCC 或 Clang)不同,TypeScript 編譯器 (tsc) 在遇到任何錯誤(型別相關的錯誤)時不會停止編譯過程。它會繼續進行並完成整個程式碼的編譯。它只會將錯誤標記並顯示在控制檯上,但會繼續進行編譯。
示例 2:物件型別檢查
由於型別擦除而可能發生的錯誤行為的另一個示例與物件屬性有關。考慮以下 TypeScript 程式碼:
interface Person { name: string; age: number; } function printInfo(person: Person) { console.log(`The name is: ${person.name}`); console.log(`The age is: ${person.age}`); console.log(`The email is: ${person.email}`); } const john = { name: "John", age: 30, email: "john@example.com" }; printInfo(john);
在執行時,printInfo函式仍將執行而不會丟擲錯誤,即使john物件具有Person介面中不存在的額外email屬性。如果printInfo函式依賴於Person介面的完整性,這可能會導致意外行為。要解決此問題,我們需要新增執行時檢查以確保傳遞給printName的person引數僅包含Person介面中的一部分屬性。
編譯後,上述 TypeScript 程式碼將生成以下 JavaScript 程式碼:
function printInfo(person) { console.log("The name is: ".concat(person.name)); console.log("The age is: ".concat(person.age)); console.log("The email is: ".concat(person.email)); } var john = { name: "John", age: 30, email: "john@example.com" }; printInfo(john);
輸出
The name is: John The age is: 30 The email is: john@example.com
在開發 TypeScript 編譯器時,其基本理念是在早期開發階段標記程式碼中的任何錯誤(包括型別和 JavaScript 錯誤),但仍將其編譯成 JavaScript 程式碼。
示例 3:泛型型別檢查
function identity<T>(value: T): T { return value; } console.log(`The returned value is: ${identity<number>("1")}`);
在此示例中,我們有一個泛型函式identity,它接受型別為T的值並將其原樣返回。但是,我們向函式傳遞了一個字串而不是數字。
由於型別資訊被擦除,該函式能夠執行而不會丟擲錯誤,但結果並非我們期望的那樣。可以透過新增執行時檢查來解決此問題,以確保傳遞給函式的值實際上是型別T。
編譯後,上述 TypeScript 程式碼將生成以下 JavaScript 程式碼:
function identity(value) { return value; } console.log("The returned value is: ".concat(identity("1")));
輸出
The returned value is: 1
結論
在使用 TypeScript 時,理解型別擦除是一個重要的概念。它可能導致難以診斷和修復的執行時錯誤,尤其是在傳遞錯誤型別的引數或物件屬性與預期介面不匹配時。為了減輕這些錯誤,務必新增執行時檢查以確保程式碼按預期執行。此外,我們可以使用諸如 TypeScript 的unknown型別和 as 關鍵字之類的工具來執行型別轉換,並確保我們的程式碼正確處理不同型別的資料。
總之,雖然 TypeScript 提供了強大的型別檢查和型別註解來幫助我們編寫更好的程式碼,但瞭解語言的侷限性以及型別擦除如何影響執行時錯誤行為非常重要。透過新增執行時檢查並使用unknown和as等工具,我們可以減輕這些錯誤並編寫更健壯、更易於維護的程式碼。