對於使用過 JavaScript 的開發者來講,對於 window.MyNamespace = window.MyNamespace || {}; 這行代碼並不會陌生。爲了不開發過程當中出現衝突,咱們通常會爲某些功能設置獨立的命名空間。dom
然而,在 TS 中對於 window.MyNamespace = window.MyNamespace || {}; 這行代碼,TS 編譯器會提示如下異常信息:ide
Property 'MyNamespace' does not exist on type 'Window & typeof globalThis'.(2339)函數
以上異常信息是說在 Window & typeof globalThis 交叉類型上不存在 MyNamespace 屬性。那麼如何解決這個問題呢?最簡單的方式就是使用類型斷言:工具
(window as any).MyNamespace = {};學習
雖然使用 any 大法能夠解決上述問題,但更好的方式是擴展 lib.dom.d.ts 文件中的 Window 接口來解決上述問題,具體方式以下:測試
declare interface Window { MyNamespace: any; }window.MyNamespace = window.MyNamespace || {};複製代碼
在 JavaScript 中,咱們能夠很容易地爲對象動態分配屬性,好比:spa
let developer = {}; developer.name = "semlinker";複製代碼
以上代碼在 JavaScript 中能夠正常運行,但在 TypeScript 中,編譯器會提示如下異常信息:對象
Property 'name' does not exist on type '{}'.(2339)複製代碼
{} 類型表示一個沒有包含成員的對象,因此該類型沒有包含 name 屬性。爲了解決這個問題,咱們能夠聲明一個 LooseObject 類型:索引
interface LooseObject { [key: string]: any}複製代碼
該類型使用 索引簽名 的形式描述 LooseObject 類型能夠接受 key 類型是字符串,值的類型是 any 類型的字段。有了 LooseObject 類型以後,咱們就能夠經過如下方式來解決上述問題:接口
interface LooseObject { [key: string]: any}let developer: LooseObject = {}; developer.name = "semlinker";複製代碼
對於 LooseObject 類型來講,它的約束是很寬鬆的。在一些應用場景中,咱們除了但願能支持動態的屬性以外,也但願可以聲明一些必選和可選的屬性。
好比對於一個表示開發者的 Developer 接口來講,咱們但願它的 name 屬性是必填,而 age 屬性是可選的,此外還支持動態地設置字符串類型的屬性。針對這個需求咱們能夠這樣作:
interface Developer { name: string; age?: number; [key: string]: any}let developer: Developer = { name: "semlinker" }; developer.age = 30; developer.city = "XiaMen";複製代碼
其實除了使用 索引簽名 以外,咱們也可使用 TypeScript 內置的工具類型 Record 來定義 Developer 接口:
// type Record = { [P in K]: T; }interface Developer extends Record<string, any> { name: string; age?: number; }let developer: Developer = { name: "semlinker" }; developer.age = 30; developer.city = "XiaMen";複製代碼
3.1 可愛又可恨的聯合類型
因爲 JavaScript 是一個動態語言,咱們一般會使用不一樣類型的參數來調用同一個函數,該函數會根據不一樣的參數而返回不一樣的類型的調用結果:
function add(x, y) { return x + y; } add(1, 2); // 3add("1", "2"); //"12"複製代碼
因爲 TypeScript 是 JavaScript 的超集,所以以上的代碼能夠直接在 TypeScript 中使用,但當 TypeScript 編譯器開啓 noImplicitAny 的配置項時,以上代碼會提示如下錯誤信息:
Parameter 'x' implicitly has an 'any' type. Parameter 'y' implicitly has an 'any' type.複製代碼
該信息告訴咱們參數 x 和參數 y 隱式具備 any 類型。爲了解決這個問題,咱們能夠爲參數設置一個類型。由於咱們但願 add 函數同時支持 string 和 number 類型,所以咱們能夠定義一個 string | number 聯合類型,同時咱們爲該聯合類型取個別名:
type Combinable = string | number;複製代碼
在定義完 Combinable 聯合類型後,咱們來更新一下 add 函數:
function add(a: Combinable, b: Combinable) { if (typeof a === 'string' || typeof b === 'string') {return a.toString() + b.toString(); } return a + b; }複製代碼
爲 add 函數的參數顯式設置類型以後,以前錯誤的提示消息就消失了。那麼此時的 add 函數就完美了麼,咱們來實際測試一下
const result = add('semlinker', ' kakuqo'); result.split(' ');複製代碼
在上面代碼中,咱們分別使用 'semlinker' 和 ' kakuqo' 這兩個字符串做爲參數調用 add 函數,並把調用結果保存到一個名爲 result 的變量上,這時候咱們想固然的認爲此時 result 的變量的類型爲 string,因此咱們就能夠正常調用字符串對象上的 split 方法。但這時 TypeScript 編譯器又出現如下錯誤信息了:
Property 'split' does not exist on type 'Combinable'. Property 'split' does not exist on type 'number'.複製代碼
很明顯 Combinable 和 number 類型的對象上並不存在 split 屬性。問題又來了,那如何解決呢?這時咱們就能夠利用 TypeScript 提供的函數重載。
3.2 函數重載
函數重載或方法重載是使用相同名稱和不一樣參數數量或類型建立多個方法的一種能力。
function add(a: number, b: number): number;function add(a: string, b: string): string;function add(a: string, b: number): string;function add(a: number, b: string): string;function add(a: Combinable, b: Combinable) { // type Combinable = string | number; if (typeof a === 'string' || typeof b === 'string') {return a.toString() + b.toString(); } return a + b; }複製代碼
在以上代碼中,咱們爲 add 函數提供了多個函數類型定義,從而實現函數的重載。在 TypeScript 中除了能夠重載普通函數以外,咱們還能夠重載類中的成員方法。
方法重載是指在同一個類中方法同名,參數不一樣(參數類型不一樣、參數個數不一樣或參數個數相同時參數的前後順序不一樣),調用時根據實參的形式,選擇與它匹配的方法執行操做的一種技術。因此類中成員方法知足重載的條件是:在同一個類中,方法名相同且參數列表不一樣。下面咱們來舉一個成員方法重載的例子:
class Calculator { add(a: number, b: number): number; add(a: string, b: string): string; add(a: string, b: number): string; add(a: number, b: string): string; add(a: Combinable, b: Combinable) { if (typeof a === 'string' || typeof b === 'string') {return a.toString() + b.toString(); }return a + b; } }const calculator = new Calculator();const result = calculator.add('Semlinker', ' Kakuqo');複製代碼
這裏須要注意的是,當 TypeScript 編譯器處理函數重載時,它會查找重載列表,嘗試使用第一個重載定義。 若是匹配的話就使用這個。 所以,在定義重載的時候,必定要把最精確的定義放在最前面。另外在 Calculator 類中,add(a: Combinable, b: Combinable){ } 並非重載列表的一部分,所以對於 add 成員方法來講,咱們只定義了四個重載方法。
歡迎你們一塊兒交流討論ts學習過程當中遇到的一些問題~~