1. Object
Object类型是所有Object类的实例的类型。它由以下两个接口来定义:
Object接口定义了Object.prototype原型对象上的属性;ObjectConstructor接口定义了Object类的属性。
也就是说我们可以通过以下两种方式定义Object类型:
let obj: Object;// 或者let obj = new Object();
Object类型包含了所有的原始(除了null和undefined)和非原始类型,所以可以给Object类型赋值为原始类型:
obj = "hell oworld";obj = 1;obj = true;obj = undefined; //Error: Type 'undefined' is not assignable to type 'Object'.obj = null; //Error: Type 'null' is not assignable to type 'Object'.
当对Object类型的变量进行赋值时,如果值对象属性名与**Object**接口中的属性冲突,则**TypeScript**编译器会提示相应的错误。如下例,对象中重写了toString方法,但是返回类型和Object中返回类型string冲突,所以报错:
obj = {toString() {return 123}} // Error: Type 'number' is not assignable to type 'string'.
还可以使用Object类型来声明数组:
let obj: Object[];obj = [123,true,"hello world",[1, 'a', false]];
2. object
object类型,它用于表示非原始类型(除了undefined,null,boolean,number,bigint,string,symbol以外的类型)。object是键值对集合,特殊在值也必须是**object**。所以如果给object类型的变量赋值原始类型将会提示错误:
let obj: object;obj = 1; // Error:Type 'number' is not assignable to type 'object'.obj = true; // Error: Type 'boolean' is not assignable to type 'object'.obj = 'a'; // Error: Type 'string' is not assignable to type 'object'.obj = undefined; //Error:Type 'undefined' is not assignable to type 'object'.obj = {a : "hell oworld",b : 1,c : true,}
object类型默认可以使用在Object类型上定义的所有属性和方法,这些属性和方法可通过JavaScript的原型链隐式地使用,但是如果在**object**中重写了原型链中的属性或者方法,那么会直接覆盖,不受原型链上的影响:
obj = {toString() {return 123;}}console.log(obj.toString()) // 123
另外在处理object类型和字符串索引对象类型的赋值操作时,也要特别注意。比如:
let strictTypeHeaders: { [key: string]: string } = {};let header: object = {};header = strictTypeHeaders; // OKstrictTypeHeaders = header; // Error: Type 'object' is not assignable to type '{ [key: string]: string; }'.
在上述例子中,最后一行会出现编译错误,这是因为{ [key: string]: string }类型相比object类型更加精确。而header = strictTypeHeaders;这一行却没有提示任何错误,是因为这两种类型都是非基本类型,object类型比{ [key: string]: string }类型更加通用。
3. {}
空类型{}描述了一个没有成员的对象,在TypeScript中可以有以下方式生成空类型:
- 没有声明变量类型,但是初始值为
{}; - 直接声明变量类型为
{}。
也就是:
let obj = {};// 或者let obj: {};
空类型{}也可以被赋值为一个原始类型:
let obj: {};obj = "hell oworld";obj = 1;obj = true;obj = undefined; //Error: Type 'undefined' is not assignable to type 'Object'.obj = null; //Error: Type 'null' is not assignable to type 'Object'.
当你试图访问这样一个对象的任意属性时,**TypeScript**会产生一个编译时错误:
const obj = {};obj.prop = "semlinker"; // Error: Property 'prop' does not exist on type '{}'.
但是,你仍然可以使⽤在Object类型上定义的所有属性和⽅法,这些属性和⽅法可通JavaScript的原型链隐式地使⽤:
const obj = {};obj.toString(); // "[object Object]"
在JavaScript中创建一个表示二维坐标点的对象很简单:
const pt = {};pt.x = 3;pt.y = 4;
然而以上代码在TypeScript中,每个赋值语句都会产生错误:
const pt = {};pt.x = 3; // Error: Property 'x' does not exist on type '{}'pt.y = 4; // Error: Property 'y' does not exist on type '{}'
这是因为pt类型是根据它的值{}推断出来的,你只可以对已知的属性赋值。这个问题怎么解决呢?有些读者可能会先想到接口,比如这样子:
interface Point {x: number;y: number;}const pt: Point = {}; // Error: Type '{}' is missing the following properties from type 'Point': x, ypt.x = 3;pt.y = 4;
很可惜对于以上的方案,TypeScript编译器仍会提示错误。那么这个问题该如何解决呢?其实我们可以直接通过对象字面量进行赋值:
const pt = {x: 3,y: 4,};
而如果你需要一步一步地创建对象,你可以使用类型断言as来消除TypeScript的类型检查:
const pt = {} as Point;pt.x = 3;pt.y = 4;
但是更好的方法是声明变量的类型并一次性构建对象:
const pt: Point = {x: 3,y: 4,};
另外在使用Object.assign方法合并多个对象的时候,你可能也会遇到以下问题:
const pt = { x: 666, y: 888 };const id = { name: "semlinker" };const namedPoint = {};Object.assign(namedPoint, pt, id);namedPoint.name; // Error: Property 'name' does not exist on type '{}'.
这时候你可以使用对象展开运算符...来解决上述问题:
const pt = { x: 666, y: 888 };const id = { name: "semlinker" };const namedPoint = {...pt, ...id}namedPoint.name // Ok
4. 思考题
题目:实现IsEmptyType<T>检查泛型T是否为{}。
type IsEmptyType<T> = // ???
简单分为三步:
- 前面所知,基本类型不能赋值给
object,但是基本类型可以赋值给{}:
type T1 = number extends object ? true : false // falsetype T2 = number extends {} ? true : false // true
因此得出:
type IsEmptyType<T> = number extends T ? true : false
现在object就被剔除了,一并剔除的还有许多,如null/undefind/string。
- 前面有写到
{}类型没有属性,所以可以通过keyof再筛选一层把其他类型筛掉:
type IsEmptyType<T> = number extends T? keyof T extends never? true: false: false
keyof {}为never,而此时通过前面的筛选留下的类型只有number/Object/unknown/any/{}几个类型通过判断返回true,而满足的keyof type为never的只有null/undefined/unknown/{},因此这次筛选只留下了unknown/{}。
- 只差最后一步剔除
unknown,unknown是顶级类型即所有类型的父类,所有类型都可以extends它,因此可以通过反向思考,写出:
type IsEmptyType<T> = number extends T? keyof T extends never? T extends {}? true: false: false: false
unkown不能 extends其他类型除了any。
