1. Object

Object类型是所有Object类的实例的类型。它由以下两个接口来定义:

  • Object接口定义了Object.prototype原型对象上的属性;
  • ObjectConstructor接口定义了Object类的属性。

也就是说我们可以通过以下两种方式定义Object类型:

  1. let obj: Object;
  2. // 或者
  3. let obj = new Object();

Object类型包含了所有的原始(除了nullundefined)和非原始类型,所以可以给Object类型赋值为原始类型:

  1. obj = "hell oworld";
  2. obj = 1;
  3. obj = true;
  4. obj = undefined; //Error: Type 'undefined' is not assignable to type 'Object'.
  5. obj = null; //Error: Type 'null' is not assignable to type 'Object'.

当对Object类型的变量进行赋值时,如果值对象属性名与**Object**接口中的属性冲突,则**TypeScript**编译器会提示相应的错误。如下例,对象中重写了toString方法,但是返回类型和Object中返回类型string冲突,所以报错:

  1. obj = {
  2. toString() {
  3. return 123
  4. }
  5. } // Error: Type 'number' is not assignable to type 'string'.

还可以使用Object类型来声明数组:

  1. let obj: Object[];
  2. obj = [
  3. 123,
  4. true,
  5. "hello world",
  6. [1, 'a', false]
  7. ];

2. object

object类型,它用于表示非原始类型(除了undefinednullbooleannumberbigintstringsymbol以外的类型)。object是键值对集合,特殊在值也必须是**object**。所以如果给object类型的变量赋值原始类型将会提示错误:

  1. let obj: object;
  2. obj = 1; // Error:Type 'number' is not assignable to type 'object'.
  3. obj = true; // Error: Type 'boolean' is not assignable to type 'object'.
  4. obj = 'a'; // Error: Type 'string' is not assignable to type 'object'.
  5. obj = undefined; //Error:Type 'undefined' is not assignable to type 'object'.
  6. obj = {
  7. a : "hell oworld",
  8. b : 1,
  9. c : true,
  10. }

object类型默认可以使用在Object类型上定义的所有属性和方法,这些属性和方法可通过JavaScript的原型链隐式地使用,但是如果在**object**中重写了原型链中的属性或者方法,那么会直接覆盖,不受原型链上的影响

  1. obj = {
  2. toString() {
  3. return 123;
  4. }
  5. }
  6. console.log(obj.toString()) // 123

另外在处理object类型和字符串索引对象类型的赋值操作时,也要特别注意。比如:

  1. let strictTypeHeaders: { [key: string]: string } = {};
  2. let header: object = {};
  3. header = strictTypeHeaders; // OK
  4. strictTypeHeaders = header; // Error: Type 'object' is not assignable to type '{ [key: string]: string; }'.

在上述例子中,最后一行会出现编译错误,这是因为{ [key: string]: string }类型相比object类型更加精确。而header = strictTypeHeaders;这一行却没有提示任何错误,是因为这两种类型都是非基本类型,object类型比{ [key: string]: string }类型更加通用。

3. {}

空类型{}描述了一个没有成员的对象,在TypeScript中可以有以下方式生成空类型:

  • 没有声明变量类型,但是初始值为{}
  • 直接声明变量类型为{}

也就是:

  1. let obj = {};
  2. // 或者
  3. let obj: {};

空类型{}也可以被赋值为一个原始类型:

  1. let obj: {};
  2. obj = "hell oworld";
  3. obj = 1;
  4. obj = true;
  5. obj = undefined; //Error: Type 'undefined' is not assignable to type 'Object'.
  6. obj = null; //Error: Type 'null' is not assignable to type 'Object'.

当你试图访问这样一个对象的任意属性时,**TypeScript**会产生一个编译时错误

  1. const obj = {};
  2. obj.prop = "semlinker"; // Error: Property 'prop' does not exist on type '{}'.

但是,你仍然可以使⽤在Object类型上定义的所有属性和⽅法,这些属性和⽅法可通JavaScript的原型链隐式地使⽤:

  1. const obj = {};
  2. obj.toString(); // "[object Object]"

JavaScript中创建一个表示二维坐标点的对象很简单:

  1. const pt = {};
  2. pt.x = 3;
  3. pt.y = 4;

然而以上代码在TypeScript中,每个赋值语句都会产生错误:

  1. const pt = {};
  2. pt.x = 3; // Error: Property 'x' does not exist on type '{}'
  3. pt.y = 4; // Error: Property 'y' does not exist on type '{}'

这是因为pt类型是根据它的值{}推断出来的,你只可以对已知的属性赋值。这个问题怎么解决呢?有些读者可能会先想到接口,比如这样子:

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5. const pt: Point = {}; // Error: Type '{}' is missing the following properties from type 'Point': x, y
  6. pt.x = 3;
  7. pt.y = 4;

很可惜对于以上的方案,TypeScript编译器仍会提示错误。那么这个问题该如何解决呢?其实我们可以直接通过对象字面量进行赋值:

  1. const pt = {
  2. x: 3,
  3. y: 4,
  4. };

而如果你需要一步一步地创建对象,你可以使用类型断言as来消除TypeScript的类型检查:

  1. const pt = {} as Point;
  2. pt.x = 3;
  3. pt.y = 4;

但是更好的方法是声明变量的类型并一次性构建对象:

  1. const pt: Point = {
  2. x: 3,
  3. y: 4,
  4. };

另外在使用Object.assign方法合并多个对象的时候,你可能也会遇到以下问题:

  1. const pt = { x: 666, y: 888 };
  2. const id = { name: "semlinker" };
  3. const namedPoint = {};
  4. Object.assign(namedPoint, pt, id);
  5. namedPoint.name; // Error: Property 'name' does not exist on type '{}'.

这时候你可以使用对象展开运算符...来解决上述问题:

  1. const pt = { x: 666, y: 888 };
  2. const id = { name: "semlinker" };
  3. const namedPoint = {...pt, ...id}
  4. namedPoint.name // Ok

4. 思考题

题目:实现IsEmptyType<T>检查泛型T是否为{}

  1. type IsEmptyType<T> = // ???

简单分为三步:

  1. 前面所知,基本类型不能赋值给object,但是基本类型可以赋值给{}
  1. type T1 = number extends object ? true : false // false
  2. type T2 = number extends {} ? true : false // true

因此得出:

  1. type IsEmptyType<T> = number extends T ? true : false

现在object就被剔除了,一并剔除的还有许多,如null/undefind/string

  1. 前面有写到{}类型没有属性,所以可以通过keyof再筛选一层把其他类型筛掉:
  1. type IsEmptyType<T> = number extends T
  2. ? keyof T extends never
  3. ? true
  4. : false
  5. : false

keyof {}never,而此时通过前面的筛选留下的类型只有number/Object/unknown/any/{}几个类型通过判断返回true,而满足的keyof typenever的只有null/undefined/unknown/{},因此这次筛选只留下了unknown/{}

  1. 只差最后一步剔除unknownunknown是顶级类型即所有类型的父类,所有类型都可以extends它,因此可以通过反向思考,写出:
  1. type IsEmptyType<T> = number extends T
  2. ? keyof T extends never
  3. ? T extends {}
  4. ? true
  5. : false
  6. : false
  7. : false

unkown不能 extends其他类型除了any