对于使用过 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学习过程当中遇到的一些问题~~