原始类型
除了最常见的 number / string / boolean / null / undefined, ECMAScript 2015(ES6)、2020 (ES11) 又分别引入了 2 个新的原始类型:symbol 与 bigint
const bigintVar1: bigint = 9007199254740991n;
const bigintVar2: bigint = BigInt(9007199254740991);
const symbolVar: symbol = Symbol('unique');
在js中null代表空,undefined代表没值。 ts中再没有开启strictNullChecks检查的情况下会被视为其他类型的子类型
//以下两个仅在关闭 strictNullChecks 时成立
const tmp3: string = null;
const tmp4: string = undefined;
void
js中 void 操作符强制将后面的函数声明转化为了表达式
void function iife() {
console.log("Invoked!");
}();
//上面的代码相当于
void((function iife(){})())
typeScript 的原始类型标注中也有 void,但与 JavaScript 中不同的是,这里的 void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值
function fn1():void{}
function fn2():void{
return
}
function fn3(){
return undefined
}
数组
//这两种方式是完全等价的,但其实更多是以前者为主,如果你将鼠标悬浮在 arr2 上,会发现它显示的类型签名是 string[]
const arr1: string[] = [];
const arr2: Array<string> = [];
元组
//对于标记为可选的成员,在 --strictNullCheckes 配置下会被视为一个 string | undefined 的类型
//也就是说,这个元组的长度可能为 1、2、3。
const arr3: [string, string?, string?] = ['lin', 'bu'];
具名元组
const arr4: [name: string, age: number, male: boolean] = ['aaaa', 599, true];
//实际上除了显式地越界访问,还可能存在隐式地越界访问,如通过解构赋值的形式
// const [ele1, ele2, ...rest] = arr4;
对象
类似于数组类型,在 TypeScript 中我们也需要特殊的类型标注来(描述对象类型),即 interface ,你可以理解为它代表了这个对象对外提供的接口结构。 描述对象类型:
- 每一个属性的值必须一一对应到接口的属性类型
- 不能有多的属性,也不能有少的属性,包括直接在对象内部声明,或是 obj1.other = 'xxx' 这样属性访问赋值的形式
除了声明属性以及属性的类型以外,我们还可以对属性进行修饰,常见的修饰包括可选(Optional) 与 只读(Readonly) 这两种。
interface IDescription {
readonly name: string;
age: number;
male: boolean;
func?: Function;//类似于上面的元组可选,在接口结构中同样通过 ? 来标记一个属性为可选:
}
const obj1: IDescription = {
name: 'aaaa',
age: 599,
male: true,
// 无需实现 func 也是合法的
};
// obj1.name = '1231' 无法分配到 "name" ,因为它是只读属性
其实在数组与元组层面也有着只读的修饰,但与对象类型有着两处不同。
- 你只能将整个数组/元组标记为只读,而不能像对象那样标记某个属性为只读。
- 一旦被标记为只读,那这个只读数组/元组的类型上,将不再具有 push、pop 等方法(即会修改原数组的方法),因此报错信息也将是类型 xxx 上不存在属性“push”这种。这一实现的本质是只读数组与只读元组的类型实际上变成了 ReadonlyArray,而不再是 Array。
type 与 interface
interface 用来描述对象、类的结构,而类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型
object,Object,{}
原型链的顶端是 Object 以及 Function,这也就意味着所有的原始类型与对象类型最终都指向 Object,在 TypeScript 中就表现为 Object 包含了所有的类型:
const tmp4: Object = 'aaa';
const tmp5: Object = 599;
const tmp6: Object = { name: 'aaa' };
const tmp7: Object = () => {};
const tmp8: Object = [];
object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些:
const tmp22: object = { name: 'aaa' };
const tmp23: object = () => {};
const tmp24: object = [];
最后是{},一个奇奇怪怪的空对象,如果你了解过字面量类型,可以认为{}就是一个对象字面量类型(对应到字符串字面量类型这样)。 否则,你可以认为使用{}作为类型签名就是一个合法的,但内部无属性定义的空对象, 这类似于 Object(想想 new Object()),它意味着任何非 null / undefined 的值:
const tmp28: {} = 'linbudu';
const tmp29: {} = 599;
const tmp30: {} = { name: 'linbudu' };
const tmp31: {} = () => {};
const tmp32: {} = [];
//虽然能够将其作为变量的类型,但你实际上无法对这个变量进行任何赋值操作:
// tmp30.name = '' 类型“{}”上不存在属性“age”。
object,Object,{}总结:
-
在任何时候都不要,不要,不要使用 Object 以及类似的装箱类型。
-
当你不确定某个变量的具体类型,但能确定它不是原始类型,可以使用 object。但我更推荐进一步区分,也就是使用 Record<string, unknown> 或 Record<string, any> 表示对象,unknown[] 或 any[] 表示数组,(...args: any[]) => any表示函数这样。
-
我们同样要避免使用{}。{}意味着任何非 null / undefined 的值,从这个层面上看,使用它和使用 any 一样恶劣。