简介

除了原始类型,对象是 JavaScript 最基本的数据结构。TypeScript 对于对象类型有很多规则。对象类型的最简单声明方法,就是使用大括号表示对象,在大括号内部声明每个属性和方法的类型

对象类型 - 图1

不能删除类型声明中存在的属性,修改属性值是可以的

对象类型 - 图2

对象的方法使用函数类型描述

对象类型 - 图3

对象类型可以使用方括号读取属性的类型

对象类型 - 图4

除了 type 命令可以为对象类型声明一个别名,TypeScript 还提供了 interface 命令,可以把对象类型提炼为一个接口

对象类型 - 图5

注意,TypeScript 不区分对象自身的属性继承的属性一律视为对象的属性

对象类型 - 图6

可选属性

可选属性等同于允许赋值为 undefined

对象类型 - 图7

TypeScript 提供编译设置 ExactOptionalPropertyTypes,只要同时打开这个设置和 strictNullChecks,可选属性就不能设为 undefined

对象类型 - 图8

只读属性

对象类型 - 图9

注意:如果属性值是一个对象,readonly 修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象

对象类型 - 图10

另一个需要注意的地方是:如果一个对象有两个引用,即两个变量对应同一个对象,其中一个变量是可写的,另一个变量是只读的,那么从可写变量修改属性,会影响到只读变量

对象类型 - 图11

如果希望属性值是只读的:除了声明时加上 readonly 关键字,还有一种方法,就是在赋值时,在对象后面加上只读断言 as const

对象类型 - 图12

属性名的索引类型

如果对象的属性非常多,一个个声明类型就很麻烦,而且有些时候,无法事前知道对象会有多少属性,比如外部 API 返回的对象。这时 TypeScript 允许采用属性名表达式的写法来描述类型,称为“属性名的索引类型”。索引类型里面,最常见的就是属性名的字符串索引

对象类型 - 图13

  1. [property: string] 的 property 表示属性名,这个是可以随便起的,它的类型是 string,即属性名类型为 string
  2. 也就是说,不管这个对象有多少属性,只要属性名为字符串,且属性值也是字符串,就符合这个类型声明
  3. JavaScript 对象的属性名(即上例的property)的类型有三种可能:string|number|symbol

对象类型 - 图14

数值索引不能与字符串索引发生冲突,必须服从后者,这是因为在 JavaScript 语言内部,所有的数值属性名都会自动转为字符串属性名

对象类型 - 图15

对象类型 - 图16

属性的索引类型写法,建议谨慎使用,因为属性名的声明太宽泛,约束太少。另外,属性名的数值索引不宜用来声明数组,因为采用这种方式声明数组,就不能使用各种数组方法以及length属性,因为类型里面没有定义这些东西

对象类型 - 图17

解构赋值

对象类型 - 图18

结构类型原则

只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)

对象类型 - 图19

TypeScript 之所以这样设计,是为了符合 JavaScript 的行为。JavaScript 并不关心对象是否严格相似,只要某个对象具有所要求的属性,就可以正确运行。这种设计有时会导致令人惊讶的结果

对象类型 - 图20

上面示例中,函数 getSum() 要求传入参数的类型是 myObj,但是实际上所有与 myObj 兼容的对象都可以传入。这会导致 const v = obj[n] 这一行报错,原因是 obj[n] 取出的属性值不一定是数值(number),使得变量v的类型被推断为any。如果项目设置为不允许变量类型推断为 any,代码就会报错。写成下面这样,就不会报错

对象类型 - 图21

上面示例就不会报错,因为函数体内部只使用了属性 x 和 y,这两个属性有明确的类型声明,保证 obj.x 和 obj.y 肯定是数值。虽然与 MyObj 兼容的任何对象都可以传入函数 getSum(),但是只要不使用其他属性,就不会有类型报错

严格字面量检查

如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错

对象类型 - 图22

如果等号右边不是字面量,而是一个变量,根据结构类型原则,是不会报错的

对象类型 - 图23

TypeScript 对字面量进行严格检查的目的,主要是防止拼写错误。一般来说,字面量大多数来自手写,容易出现拼写错误,或者误用 API

对象类型 - 图24

如果你确认字面量没有错误,也可以使用类型断言规避严格字面量检查

对象类型 - 图25

由于严格字面量检查,字面量对象传入函数必须很小心,不能有多余的属性

对象类型 - 图26

编译器选项 suppressExcessPropertyErrors,可以关闭多余属性检查。下面是它在 tsconfig.json 文件里面的写法

对象类型 - 图27

最小可选属性规则

根据“结构类型”原则,如果一个对象的所有属性都是可选的,那么其他对象跟它都是结构类似的

对象类型 - 图28

为了避免这种情况,TypeScript 2.4 引入了一个“最小可选属性规则”,也称为“弱类型检测”(weak type detection)https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html#weak-type-detection

对象类型 - 图29

上面示例中,对象 opts 与类型 Options 没有共同属性(d 属性),赋值给该类型的变量就会报错。报错原因是,如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。如果想规避这条规则

  • 要么在类型里面增加一条索引属性([propName: string]: someType
  • 要么使用类型断言(opts as Options

空对象

对象类型 - 图30

回到本节开始的例子,这种写法其实在 JavaScript 很常见:先声明一个空对象,然后向空对象添加属性但是,TypeScript 不允许动态添加属性,所以对象不能分步生成,必须生成时一次性声明所有属性

对象类型 - 图31

如果确实需要分步声明,一个比较好的方法是,使用 扩展运算符(…)合成一个新对象

对象类型 - 图32

如果想强制使用没有任何属性的对象,可以采用下面的写法

对象类型 - 图33