# 基础类型 (8 种)
JavaScript
语言(注意,不是 TypeScript
)将值分成 8 种类型。
boolean
布尔string
字符串number
数量bigint
长整型数字symbol
象征object
对象undefined
未定义的null
零值
TypeScript
继承了 JavaScript
的类型设计,以上 8种类型
可以看作 TypeScript
的基本类型。
注意,上面所有类型的名称都是小写字母,首字母大写的 Number、String、Boolean 等在 JavaScript
语言中都是内置对象,而不是类型名称。
- 布尔值
let isDone: boolean = false; |
- 字符串
(普通字符串和模板字符串都属于 string 类型)
let str1: string = "bob"; | |
let str2: string = `${str1} world`; |
- number 类型
let decLiteral: number = 6; | |
let hexLiteral: number = 0xf00d; | |
let binaryLiteral: number = 0b1010; | |
let octalLiteral: number = 0o744; |
- bigint 类型
bigint 类型是 ES2020 标准引入的。 JavaScript
版本不能低于 ES2020
bigint 类型包含所有的大整数。
const x:bigint = 123n; | |
const y:bigint = 0xffffn; |
bigint 与 number 类型不兼容。
const x:bigint = 123; // 报错 | |
const y:bigint = 3.14; // 报错 |
- symbol 类型
symbol 类型包含所有的 Symbol 值。
const x:symbol = Symbol(); |
- object 类型
根据 JavaScript
的设计,object 类型包含了所有对象、数组和函数。
const x:object = { foo: 123 }; | |
const y:object = [1, 2, 3]; | |
const z:object = (n:number) => n + 1; |
上面示例中,对象、数组、函数都属于 object 类型。
关于数组,TypeScript 有专门类型定义。
// 第一种写法 | |
let list: number[] = [1, 2, 3]; | |
list.push(4); | |
list.length = 2; | |
list[1] = 5; |
采用 number[]
定义了数组成员是动态的,可以增加、减少、修改成员,但成员类型必须为 number 类型。
// 第二种写法 | |
let arr:Array<number> = [1, 2, 3]; |
- undefined
- null
let u: undefined = undefined; | |
let n: null = null; |
注意,如果没有声明类型的变量,被赋值为 undefined
或 null
,在关闭编译设置 noImplicitAny 和 strictNullChecks 时,它们的类型会被推断为 any。
// 关闭 noImplicitAny 和 strictNullChecks | |
let a = undefined; // any | |
const b = undefined; // any | |
let c = null; // any | |
const d = null; // any |
如果希望避免这种情况,则需要打开编译选项 strictNullChecks。
// 打开编译设置 strictNullChecks | |
let a = undefined; // undefined | |
const b = undefined; // undefined | |
let c = null; // null | |
const d = null; // null |
元组
let x: [string, number]; | |
x = ["hello", 10]; // OK | |
x = [10, "hello"]; // Error |
any
let notSure: any = 4; | |
notSure = "maybe a string instead"; | |
notSure = false; // okay, definitely a boolean |
# 类型推论
变量初始化类型推断,如果没有明确声明类型, TypeScript
会根据变量初始值推断类型。
// 变量 | |
let someValue = 4;// 初始化时,根据赋值自动推断为 number 类型 | |
someValue = "maybe a string instead";//error | |
// 数组 | |
let list = [1, 2, 3];// 初始化时,根据赋值自动推断为 number [] 类型 | |
list.push("hello");//error |
数组变量未声明类型,且为空数组,TypeScript 会推断为 any[]
类型。
let list = [];// 推断为 any [] | |
list.push(1);// 赋值后,推断为 number [] 类型 | |
list.push("hello");// 再次赋值,推断为 (number|string)[] 类型 |
# 只读数组,const 断言
JavaScript
中, const
定义的变量,不能重新赋值,但可以修改其成员。
const arr = [1, 2, 3];//number[] | |
arr[0] = 4;//ok | |
arr[0] = "hello world!";//error, | |
arr.push(5);//error |
TsypeScript
允许声明只读数组,方法是在数组类型前加上 redayonly
关键字。
const arr: readonly number[] = [1, 2, 3];//readonly number[] | |
arr[0] = 4;//error | |
arr.push(5);//error | |
delete arr[0];//error |
TsypeScript
将 readonly number[]
视为 number[]
的父类,子类型具有父类行的特征并具有自己的特征 pop\push
, 所以子类可以赋值给父类型,反过来不行。
let arr:readonly number[] = [1, 2, 3]; | |
let arr2:readonly number[] = arr;//ok | |
arr = arr2;//error | |
//-------------------------- | |
function getSum(s:number[]) { | |
// ... | |
} | |
const arr:readonly number[] = [1, 2, 3]; | |
getSum(arr) //error, 等价于 getSum (s:number []=arr:readonly number []) |
number[]
作为子类继承 readonly number[]
父类,而父类没有 pop()
、 push()
方法,所以两者并不完全等价。
TypeScript 中,const 定义的变量,不能重新赋值,也不能修改其成员。
const arr: readonly number[] = [1, 2, 3]; | |
arr[0] = 4;//error |
# 只读数组,泛型写法
// error | |
const arr:readonly Array<number> = [0, 1]; | |
// method 1 | |
const a1:ReadonlyArray<number> = [0, 1]; | |
// method 2 | |
const a2:Readonly<number[]> = [0, 1]; | |
//method 3 使用 “const 断言” 实现 | |
const arr1 = [0, 1] as const; | |
arr1[0] = [2]; // 报错 |
# 类型断言
对于没有类型声明的值, TypeScript
会进行类型推断,很多时候得到的结果,未必是开发者想要的,类型断言可以用来告诉编译器变量的类型,可以避免编译器进行类型推断。
type T = 'a'|'b'|'c'; | |
let foo = 'a'; | |
//let bar:T = foo; //error bar 为 T 类型,foo 推断为 string 类型 | |
let bar:T = foo as T; //ok 断言 foo 为 T 类型 |
# 断言语法
// 语法一:`< 类型 > 值` | |
<Type>value |
// 语法二:`值 as 类型` | |
value as Type // 推荐写法 |
对象类型有严格字面量检查,如果存在额外的属性会报错.
const p:{ x: number } = { x: 0, y: 0 };// error |
断言可以绕过字面量检查,存在类型断言,就没有严格字面量检查了,所以不报错。
const p0:{ x: number } = | |
{ x: 0, y: 0 } as { x: number };//ok 断言使得等号左右两边类型一致 | |
const p1:{ x: number } = | |
{ x: 0, y: 0 } as { x: number; y: number };//ok 断言使得等号左右两边类型一致 |
# 值类型
TypeScript 规定,单个值也是一种类型,称为 “值类型”。
let x:'hello'; | |
x = 'hello'; // 正确 | |
x = 'world'; // 报错 |
TypeScript 推断类型时,遇到 const
命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。
//x 的类型是 "https" | |
const x = 'https'; | |
//y 的类型是 string | |
const y:string = 'https'; |
这样推断是合理的,因为 const
命令声明的变量,一旦声明就不能改变,相当于常量。值类型就意味着不能赋为其他值。
注意, const
命令声明的变量,如果赋值为对象,并不会推断为值类型。
//x 的类型是 {foo: number} | |
const x = { foo: 1 }; |
变量 x
没有被推断为值类型,而是推断属性 foo
的类型是 number
。这是因为
JavaScript
里面, const
变量赋值为对象时,属性值是可以改变的。
const x:5 = 4 + 1; // 报错 |
上面示例中,等号左侧的类型是数值 5
,等号右侧 4 + 1
的类型, TypeScript
推测为 number
。由于 5
是 number
的子类型, number
是 5
的父类型,父类型不能赋值给子类型,所以报错了.
但是,反过来是可以的,子类型可以赋值给父类型。
let x:5 = 5; | |
let y:number = 4 + 1; | |
x = y; // 报错 | |
y = x; // 正确 |
如果一定要让子类型可以赋值为父类型的值,就要用到类型断言
const x:5 = (4 + 1) as 5; // 正确 |
上面示例中,在 4 + 1
后面加上 as 5
,就是告诉编译器,可以把 4 + 1
的类型视为值类型 5
,这样就不会报错了。
只包含单个值的值类型,用处不大。实际开发中,往往将多个值结合,作为联合类型使用。
# 联合类型
联合类型(union types)指的是多个类型组成的一个新类型,使用符号 | 表示。
联合类型 A|B
表示,任何一个类型只要属于 A
或 B
,就属于联合类型 A|B
。
// 基本类型的联合类型 | |
let myFavoriteNumber: string | number; | |
myFavoriteNumber = 'seven'; // OK | |
myFavoriteNumber = 7; // OK | |
// 值类型的联合类型 | |
let set : true| false; | |
let name : 'Tom' | 'Jerry' | 'Spike'; | |
let color: 'red' | 'blue' | 'green'; |
打开编译选项 strictNullChecks
后,其他类型的变量不能赋值为 undefined
或 nul
l。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。
let name:string|null; | |
name = 'John'; | |
name = null; | |
// 多行书写联合类型 | |
let x: | |
| 'one' | |
| 'two' | |
| 'three' | |
| 'four'; |
如果一个变量有多种类型,读取该变量时,往往需要进行 “类型缩小”(type narrowing),区分该值到底属于哪一种类型,然后再进一步处理。
function printId( | |
id:number|string | |
) { | |
console.log(id.toUpperCase()); // 报错 | |
} | |
// 类型缩小 | |
function printId( | |
id:number|string | |
) { | |
if (typeof id === 'string') { | |
console.log(id.toUpperCase()); | |
} else { | |
console.log(id); | |
} | |
} |
# 交叉类型
交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号 &
表示。
交叉类型 A&B
表示,任何一个类型必须同时属于 A
和 B
,才属于交叉类型 A&B
,即交叉类型同时满足 A 和 B 的特征。
let x:number&string; |
上面示例中,变量 x
同时是数值和字符串,这当然是不可能的,所以 TypeScript
会认为 x 的类型实际是 never
。
交叉类型的主要用途是表示对象的合成。
let obj: | |
{ foo: string } & | |
{ bar: string }; | |
obj = { | |
foo: 'hello', | |
bar: 'world' | |
}; |
上面示例中,变量 obj
同时具有属性 foo
和属性 bar
。
交叉类型常常用来为对象类型添加新属性。
type A = { foo: number }; | |
type B = A & { bar: number }; |
上面示例中,类型 B
是一个交叉类型,用来在 A
的基础上增加了属性 bar
。
# type 命令
type 命令用来定义一个类型的别名。
type Age = number; | |
let age:Age = 55; |
上面示例中, type
命令为 number
类型定义了一个别名 Age
。这样就能像使用 number
一样,使用 Age
作为类型。
别名的作用域是块级作用域。这意味着,代码块内部定义的别名,影响不到外部。
type Color = 'red'; | |
if (Math.random() < 0.5) { | |
type Color = 'blue'; | |
} |
别名支持使用表达式,也可以在定义一个别名时,使用另一个别名,即别名允许嵌套。
type World = "world"; | |
type Greeting = `hello ${World}`; |
# typeof 运算符
JavaScript
语言中, typeof
运算符是一个一元运算符,返回一个字符串,代表操作数的类型。
typeof 'foo'; // 'string' |
JavaScript
里面, typeof
运算符只可能返回八种结果,而且都是字符串。
typeof undefined; // "undefined" | |
typeof true; // "boolean" | |
typeof 1337; // "number" | |
typeof "foo"; // "string" | |
typeof {}; // "object" | |
typeof parseInt; // "function" | |
typeof Symbol(); // "symbol" | |
typeof 127n // "bigint" |
由于编译时不会进行 JavaScript
的值运算,所以 TypeScript
规定,typeof 的参数只能是标识符,不能是需要运算的表达式。
type T = typeof Date(); // 报错 |
上面示例会报错,原因是 typeof
的参数不能是一个值的运算式,而 Date()
需要运算才知道结果。
另外, typeof
命令的参数不能是类型。
type T = typeof Date(); // 报错 |
# 块圾类型声明
TypeScript 支持块级类型声明,即类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效。
if (true) { | |
type T = number; | |
let v:T = 5; | |
} else { | |
type T = string; | |
let v:T = 'hello'; | |
} |
# 类型兼容
TypeScript 的类型存在兼容关系,某些类型可以兼容其他类型。
type T = number|string; | |
let a:number = 1; | |
let b:T = a; |
TypeScript 的一个规则是,凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行。
let a:'hi' = 'hi'; | |
let b:string = 'hello'; | |
b = a; // 正确 | |
a = b; // 报错 |
上面示例中, hi
是 string
的子类型, string
是 hi
的父类型。所以,变量 a
可以赋值给变量 b
,但是反过来就会报错。