any、unknown、never
any 类型
any类型表示没有任何限制,该类型的变量可以赋予任意类型的值any的使用场景- 特殊场景需要关闭变量的类型检查,使用
any - 迁移老
js项目,先设置为any,再一步一步迁移到ts
- 特殊场景需要关闭变量的类型检查,使用
- 从集合论的角度讲,
any类型可以看作其他类型的全集,ts 将其叫做"顶层类型" - 类型推断
- 如果 ts 的自动类型推断无法推断出变量的类型,就会将其推断为
any类型 - 如果想要阻止推断为
any类型,可以开启noImplicitAny(不能存在隐式any)
- 如果 ts 的自动类型推断无法推断出变量的类型,就会将其推断为
unknown 类型
unknown跟any的相似之处,在于所有类型的值都可以分配给unknown类型unknown类型跟any类型的不同之处在于,它不能直接使用,主要有以下几个限制unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)- 不能直接调用
unknown类型变量的方法和属性 unknown类型变量能够进行的运算是有限的,只能进行比较运算(运算符==、===、!=、!==、||、&&、?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错
unknown类型的使用需要经过"类型缩小",缩小unknown变量的类型范围,确保不会出错
let a: unknown = 1;
// 缩小为 number 类型
if (typeof a === "number") {
a.toFixed(1);
}
// 缩小为 string 类型
if (typeof a === "string") {
a.trim();
}
- 在集合论上,
unknown也可以视为所有其他类型(除了any)的全集,所以它和any一样,也属于 TypeScript 的顶层类型
never 类型
- 为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。
- 使用场景
- 在一些类型运算之中,保证类型运算的完整性(如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于
never类型) - 不可能返回值的函数,返回值的类型就可以写成
never
- 在一些类型运算之中,保证类型运算的完整性(如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于
never可以赋值给任意类型,因为在集合论中,never为空集,空集是所有集合的子集,所以可以赋值never是一个底层类型
基本类型和包装类型
ts 中的基本类型和 js 的基本类型是一样的
boolean包含true和falsestring字符串number数字类型,包括整数和浮点数bigint大整数symbolobject包括对象、数组、函数undefined、null,各自代表一种类型- 在关闭
noImplicitAny和strictNullChecks的时候,如果变量设置为undefined或者null,变量类型为any,因此需要开启
- 在关闭
包装类型
js 的8种类型,undefined 和 null 是两个特殊值,object 属于复合类型,剩下的5种属于原始类型,代表最基本的,不可再分的值
这5种基本类型,又有对应的包装类型,而 Symbol 和 bigint 不能作为构造函数使用,也就没有对应的包装类型,所以 ts 中基本类型的包装类型包括以下
BooleanStringNumber
包装类型和字面量类型 TypeScript 对五种原始类型分别提供了大写和小写两种类型。
- Boolean 和 boolean
- String 和 string
- Number 和 number
- BigInt 和 bigint
- Symbol 和 symbol 其中,大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象
注意:symbol 和 bigint 的大小写没有区别,原因上面解释了 (不能作为构造函数使用)
Object 和 object
大写 Object
- 大写的
Object类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型。 - 除了
undefined和null这两个值不能转为对象,其他任何值都可以赋值给Object类型 - 空对象
{}是Object类型的简写形式
小写 object
- 小写的
object类型代表 JavaScript 里面的狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值
undefined 和 null
undefined 和 null 既是值,又是类型。
- 作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为
undefined或null。但是可能导致无法取到变量上的一些属性和方法。 - 因此提供了
strictNullChecks选项,让undefined和null不能赋值给其他类型变量,防止报错
值类型
TypeScript 规定,单个值也是一种类型,称为“值类型”。
TypeScript 推断类型时,遇到 const 命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。
// x 的类型是 "https"
const x = 'https';
只包含单个值的值类型,用处不大。实际开发中,往往将多个值结合,作为联合类型使用。
联合类型 (并集)
联合类型:一个值可以是多种类型中的任意一种(“或”的关系)
联合类型(union types)指的是多个类型组成的一个新类型,使用符号 | 表示。
联合类型 A|B 表示,任何一个类型只要属于 A 或 B,就属于联合类型 A|B。
“类型缩小”是 TypeScript 处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种“类型放大”(type widening),处理时就需要“类型缩小”(type narrowing)。
交叉类型 (交集)
交叉类型:一个值必须同时满足所有类型(“且”的关系),拥有所有成员
交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号 & 表示。
交叉类型 A&B 表示,任何一个类型必须同时属于 A 和 B,才属于交叉类型 A&B,即交叉类型同时满足 A 和 B 的特征。
交叉类型的主要用途是表示对象的合成。
数组
声明方式
- 第一种
let arr: number[] = [1,2,3]
- 第二种
let arr: Array<number> = [1,2,3]
只读数组
声明方式:
- 使用
readonly关键字
const arr: readonly number[] = [0, 1]
- 使用只读泛型
const arr: Readonly<number[]> = [0, 1]
const arr: ReadonlyArray<number[]> = [0, 1]
- 使用
const断言
const arr = [0, 1] as const;
元组
它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
比如:
const s:[string, string, boolean] = ['a', 'b', true];
元组有以下语法:
- 元组成员的类型可以添加问号后缀(
?),表示该成员是可选的。
注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
let a:[number, number?] = [1];
- 使用
...拓展运算符,可以创建表示无限成员数量的元组
扩展运算符(
...)用在元组的任意位置都可以,它的后面只能是一个数组或元组
type t1 = [string, number, ...boolean[]];
type t2 = [string, ...boolean[], number];
type t3 = [...boolean[], string, number];
- 元组的成员可以添加成员名,这个成员名是说明性的,可以任意取名,没有实际作用。
type Color = [
red: number,
green: number,
blue: number
];
const c:Color = [255, 255, 255];
- 元组可以通过方括号,读取成员类型。由于元组的成员都是数值索引,即索引类型都是
number,所以可以像下面这样读取。
type Tuple = [string, number];
type Age = Tuple[1]; // number
type Tuple = [string, number, Date];
type TupleEl = Tuple[number]; // string|number|Date
-
只读元组,语法和只读数组一样,这里不重复了
-
成员数量的推断
正常使用元组可以推断出成员数量,但是如果使用了可选成员或者拓展运算符,就获取不到成员数量,会爆 ts 错误
对象
ts 中对象包括以下常用语法
- 可选属性,如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号。
type User = {
firstName: string;
lastName?: string;
};
// 等同于
type User = {
firstName: string;
lastName: string|undefined;
};
使用方式
// 写法一
let firstName = (user.firstName === undefined)
? 'Foo' : user.firstName;
let lastName = (user.lastName === undefined)
? 'Bar' : user.lastName;
// 写法二
let firstName = user.firstName ?? 'Foo';
let lastName = user.lastName ?? 'Bar';
-
只读属性,属性名前面加上
readonly关键字,表示这个属性是只读属性,不能修改。 -
属性名索引,除了
string,还可以是number、symbol
type MyObj = {
[property: string]: string
};
-
解构赋值声明类型,只能给整体声明类型,不能给单个变量指定类型
-
“结构类型“原则,只要对象 B 满足对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则。比较抽象,反正大概就是下面这样的代码
const B = {
x: 1,
y: 1
};
const A:{ x: number } = {
x: 1,
y: 1 // 字面量方式声明报错
}
const A: {x: number } = B // 变量方式赋值不报错
- 严格字面量检查,如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错。刚好和"解构类型"原则相反
const point:{
x:number;
y:number;
} = {
x: 1,
y: 1,
z: 1 // 报错
};
- 最小可选属性原则,如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。
函数
ts 函数类型的两种声明方式
- 普通函数的类型声明,类型直接写在函数上
function add(x: number, y: number): number {
return x + y
}
- 箭头函数的声明方式,也有三种方式
// 方式1
const add = (x: number, y: number): number => {
return x + y
}
// 方式2
const add: (x: number, y: number) => number = (x, y) => {
return x + y
}
// 方式3
type Add = (x: number, y: number) => number
const add: Add = (x, y) => {
return x + y
}
函数也可以使用 typeof 关键字获取类型
function add(x: number, y: number): number {
return x + y
}
const add2: typeof add = (x, y) => {
return x + y
}
可选参数
function f(x?:number) {
// ...
}
f() // 正确
function f(x:number|undefined) {
return x;
}
f() // 错误 如果是这样定义,需要传入undefined
参数默认值,设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。
function createPoint(
x:number = 0,
y:number = 0
):[number, number] {
return [x, y];
}
createPoint() // [0, 0]
参数解构,写法如下
function f(
[x, y]: [number, number]
) {
// ...
}
function sum(
{ a, b, c }: {
a: number;
b: number;
c: number
}
) {
console.log(a + b + c);
}
// 默认值写法
function add({ x = 10, y = 10 }: { x?: number, y?: number }): number {
return x + y
}
console.log(add({}))
rest 参数,rest 参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
// rest 参数为数组
function joinNumbers(...nums:number[]) {
// ...
}
// rest 参数为元组
function f(...args:[boolean, number]) {
// ...
}
// 最后一个参数可选
function f(
...args: [boolean, string?]
) {}
// 利用灵活的元组的rest参数
function f(...args:[boolean, ...string[]]) {
// ...
}
f(false, '1', '2', '4')
readonly 只读参数
function arraySum(
arr:readonly number[]
) {
// ...
arr[0] = 0; // 报错
}
void 类型,void 类型表示函数没有返回值,并不是说函数不返回值,而是说返回值不重要
type voidFunc = () => void;
const f:voidFunc = () => {
return 123; // 不报错
};
function f():void {
return true; // 报错
}
const f3 = function ():void {
return true; // 报错
};
never 类型,表示肯定不会出现的值,比如函数的返回值,包括
- 抛出错误的函数
- 无限执行的函数
never类型不同于void类型。前者表示函数没有执行结束,不可能有返回值;后者表示函数正常执行结束,但是不返回值,或者说返回undefined
高阶函数,一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数(higher-order function)
函数重载,有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为
function reverse(str:string):string;
function reverse(arr:any[]):any[];
function reverse(
stringOrArray:string|any[]
):string|any[] {
if (typeof stringOrArray === 'string')
return stringOrArray.split('').reverse().join('');
else
return stringOrArray.slice().reverse();
}
构造函数的类型
- 构造函数的类型写法,就是在参数列表前面加上
new命令 - 构造函数还有另一种类型写法,就是采用对象形式
- 某些函数既是构造函数,又可以当作普通函数使用,比如
Date(),可以两种函数的类型定义可以写在对象里
class Foo {
b: string;
}
// 构造函数的类型
type FooContructor = new () => Foo;
// 构造函数类型的对象写法
type FooContructor = {
// 构造函数的定义
new (): Foo;
// 普通函数的定义
(): string;
};
function create(c: FooContructor): Foo {
return new c();
}
const foo = create(Foo);
enum 类型
Enum 结构,用来将相关常量放在一个容器里面,方便使用,默认如果不赋初始值,枚举值将依次增加,比如
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
Enum 结构本身也是一种类型。比如,上例的变量 c 等于 1,它的类型可以是 Color,也可以是 number。
let c:Color = Color.Green; // 正确
let c:number = Color.Green; // 正确
Enum 编译之后会变成 JS 代码,这是一个很特别的点,因为 TS 的定位是 JS 语言的类型增强,所以谨慎使用 Enum 结构,很大程度上,Enum 可以被对象的 as const 断言替代,它也可以作为类型
enum Foo {
A,
B,
C,
}
const Bar = {
A: 0,
B: 1,
C: 2,
} as const;
// 对象的as const 断言写法也可以作为类型,即使用 typeof
const Bar = {
A: 0,
B: 1,
C: 2,
} as const;
const a: typeof Bar = {
A: 0,
}
Enum 之前也可以加上
注意:TypeScript 5.0 之前,Enum 有一个 Bug,就是 Enum 类型的变量可以赋值为任何数值。
enum Bool {
No,
Yes
}
function foo(noYes:Bool) {
// ...
}
foo(33); // TypeScript 5.0 之前不报错
enum 的值
enum 支持字符串和数字两种,数字可以是除了 bigint 之外的所有数字
对于数字类型的枚举来说,enum 支持反向映射 (即反向查找),而字符串类型不行
enum A {
white=4
}
console.log(A[4]) // "white"
enum 的合并
多个同名的 Enum 结构会自动合并 作用:
- 补充外部定义的 Enum 结构 注意事项:
- Enum 结构合并时,只允许其中一个的首成员省略初始值,否则报错。
- 同名 Enum 合并时,不能有同名成员,否则报错。
- 同名 Enum 合并的另一个限制是,所有定义必须同为 const 枚举或者非 const 枚举,不允许混合使用。
反向映射
即使用数字类型的 enum,不仅可以通过键找到值,还可以通过值找到键
enum A {
Red,
Blue,
Green,
}
console.log(A.Red) // 0
console.log(A[0]); // Red
eunm 的本质
enum 本质是将枚举编译成了一个对象
比如如下代码
enum A {
Red,
Blue=2,
Green
}
console.log(A)
// 打印结果为:
/**
{
"0": "Red",
"2": "Blue",
"3": "Green",
"Red": 0,
"Blue": 2,
"Green": 3
}
*/
enum 和 const enum 的编译区别是
// ts代码
const enum A {
Red,
Blue,
Green,
}
console.log(A.Red)
console.log(A.Blue)
console.log(A.Green)
// enum编译结果
var A;
(function (A) {
A[A["Red"] = 0] = "Red";
A[A["Blue"] = 1] = "Blue";
A[A["Green"] = 2] = "Green";
})(A || (A = {}));
console.log(A.Red);
console.log(A.Blue);
console.log(A.Green);
// const enum 编译结果
console.log(0 /* A.Red */);
console.log(1 /* A.Blue */);
console.log(2 /* A.Green */);
其中 enum 的编译结果咋一眼看过去看不懂,实际上可以分开分析
var A // 定义了 和enum名称一样的全局变量
// 立即执行函数,对A赋值
(function (A) {
...
})(A || (A = {})) // 如果A有值传入A(用于枚举合并的场景),否则传入兜底的空对象
// 对A键的赋值
A[A["Red"] = 0] = "Red";
// 分为两步分析
// 1. 包括 A["Red"] = 0 所以字符串类型的键名就获得了键值
// 2. 使用键值再进行赋值,实现反向映射,A[A["Red"] = 0] = "Red",相当于A[0] = "Red"
// 而如果是字符串作为键值,只有这一层,没有反向映射
A["Red"] = "red";