数据类型
1.原始类型
1.1 概念
JavaScript 语言(注意,不是 TypeScript)将值分成 8 种类型:null、undefined、boolean、number、string、symbol、bigint。
TypeScript 继承了 JavaScript 的类型设计,以上 8 种类型可以看作 TypeScript 的基本类型。
注意,上面所有类型的名称都是小写字母,首字母大写的Number、String、Boolean等在 JavaScript 语言中都是内置对象,而不是类型名称。
另外,undefined 和 null 既可以作为值,也可以作为类型,取决于在哪里使用它们。
这 8 种基本类型是 TypeScript 类型系统的基础,复杂类型由它们组合而成。
1.2 定义语法
修饰符 变量名: 数据类型 = 值1.3 boolean
boolean类型只包含true和false两个布尔值。
const x: boolean = true;
const y: boolean = false;1.4 string
string类型包含所有字符串。
const x: string = "hello";
const y: string = `${x} world`;上面示例中,普通字符串和模板字符串都属于 string 类型。
1.5 number
number类型包含所有整数和浮点数。
const x: number = 123;
const y: number = 3.14;
const z: number = 0xffff;上面示例中,整数、浮点数和非十进制数都属于 number 类型。
1.6 bigint
bigint 类型包含所有的大整数。
const x: bigint = 123n;
const y: bigint = 0xffffn;上面示例中,变量x和y就属于 bigint 类型。
TIP
bigint 与 number 类型不兼容。
const x: bigint = 123; // 报错
const y: bigint = 3.14; // 报错上面示例中,bigint类型赋值为整数和小数,都会报错。
注意,bigint 类型是 ES2020 标准引入的。如果使用这个类型,TypeScript 编译的目标 JavaScript 版本不能低于 ES2020(即编译参数target不低于es2020)。
1.7 symbol
symbol 类型包含所有的 Symbol 值。
const x: symbol = Symbol();上面示例中,Symbol()函数的返回值就是 symbol 类型。
1.8 undefined,null
undefined 和 null 是两种独立类型,它们各自都只有一个值。
undefined 类型只包含一个值undefined,表示未定义(即还未给出定义,以后可能会有定义)。
let x: undefined = undefined;上面示例中,变量x就属于 undefined 类型。两个undefined里面,第一个是类型,第二个是值。
null 类型也只包含一个值null,表示为空(即此处没有值)。
const x: null = null;上面示例中,变量x就属于 null 类型。
TIP
注意,如果没有声明类型的变量,被赋值为undefined或null,它们的类型会被推断为any。
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上面示例中,打开编译设置strictNullChecks以后,赋值为undefined的变量会被推断为undefined类型,赋值为null的变量会被推断为null类型。
2.对象类型
2.1 Object
const a: { test1: number; test2: string } = { test1: 11, test2: 'abcafsd' };2.2 Array
声明元素是基本类型的数组
const arr1: number[] = [11, 22]
const arr2: string[] = ['hello', 'world']
const arr3: boolean[] = [true, false]声明联合类型数组,元素的类型可以是多个,多个类型用|运算符合并
const arr1: (number | string)[] = [11, 'hello']
const arr2: (number | string | boolean)[] = [11, 'hello', true]声明包含指定对象类型的数组
const arr1: {a:number,b:number}[] = [{a:1,b:2},{a:1,b:2}]声明二维数组
const arr1: [number, number, number][] = [[1, 2, 3], [4, 5, 6]]
const arr2: { a: number; b: number }[][] = [
[
{ a: 1, b: 2 },
{ a: 3, b: 4 },
],
]2.3 RegExp
const reg: RegExp = /\d/2.4 Date
const b: Date = new Date()2.5 Number
const num: Number = new Number(123)
const num1: Number = 4562.6 String
const str: String = new String(11)
const str1: String = "123456"2.7 Function
语法:
function 函数名(形参1: 数据类型1, 形参2: 数据类型2): 返回值类型 {
return 值
}参数不确定传不传,在形参后面添加?
function 函数名(age?:number):number {
return age
}例子
// 形参、返回值
function fn1(name:string,age:number):number {
return age
}
const a:number = fn1('张三', 18)}
// 函数可选参数
function fn2(name:string,age:number, sex?:string):number {
return age
}
const b:number = fn2('张三', 18)
// 箭头函数
const fn3:(name:stirng,age:number)=>number = function() {
return 666
}
const data7 = (num1: number, num2: number = 66):string => {
return '我就不用形参'
}
data7(11,222)
data7(11)3.新增类型
ts 新增数据类型:
string |number | ...:联合类型- any:任意类型
- unknown:不详的/未知类型
- type:类型别名自定义类型
- tuple:元组
- enum:枚举
- void:无类型
- never:永不存在的值的类型 。通常是抛出异常或者永远不会有返回值的类型
3.1 tuple
元组(tuple)是 TypeScript 特有的数据类型,JavaScript 没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
const data:[number,number,string]=[1,2,'3']注意
元组类型的写法,与数组有一个重大差异。数组的成员类型写在方括号外面(number[]),元组的成员类型是写在方括号里面([number])。
TypeScript 的区分方法是,成员类型写在方括号里面的就是元组,写在外面的就是数组。
typescript
let a: [number] = [1];元组成员的类型可以添加问号后缀(?),表示该成员是可选的。
let a: [number, number?] = [1];注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
type myTuple = [number, number, number?, string?];元组也可以是只读的,不允许修改,有两种写法。
// 写法一
type t = readonly [number, string];
// 写法二
type t = Readonly<[number, string]>;3.2 enum
Enum用来是Ts新增数据类型枚举,常用来表示一些常量
3.2.1 枚举定义
最基本的枚举定义如下
enum Week {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}默认情况下,枚举的值从0开始,依次递增,但我们可以给每个元素一个自定义的值
enum Week {
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7,
}也可以给中文
enum Week {
Monday = "周一",
Tuesday = "周二",
Wednesday = "周三",
Thursday = "周四",
Friday = "周五",
Saturday = "周六",
Sunday = "周末",
}3.2.2 枚举访问
枚举的访问类似于对象,都是键值对
enum Week {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
console.log(Week.Monday); // 0
console.log(Week.Tuesday); // 1
console.log(Week.Wednesday); // 2
console.log(Week.Thursday); // 3
console.log(Week.Friday); // 4
console.log(Week.Saturday); // 5
console.log(Week.Sunday); // 6
// 也可以通过[]访问
console.log(Week['Monday']) // 0
console.log(Week['Tuesday']) // 1
console.log(Week['Wednesday']) // 2
console.log(Week['Thursday']) // 3
console.log(Week['Friday']) // 4
console.log(Week['Saturday']) // 5
console.log(Week['Sunday']) // 63.2.3 Enum字符串
Enum 成员的值除了设为数值,还可以设为字符串。也就是说,Enum 也可以用作一组相关字符串的集合。
除了数值和字符串,Enum 成员不允许使用其他值(比如 Symbol 值)。
enum Time {
Morning = "早上",
Afternoon = "下午",
Evening = "晚上",
}
console.log(Time.Morning) // 早上
console.log(Time.Afternoon) // 下午
console.log(Time.Evening) // 晚上Enum 成员可以是字符串和数值混合赋值。
enum Enum {
One = "One",
Two = "Two",
Three = 3,
Four = 4,
}注意
字符串枚举的所有成员值,都必须显式设置。如果没有设置,成员值默认为数值,且位置必须在字符串成员之前。
enum Foo {
A, // 0
B = "hello",
C, // 报错
}3.2.4 反向映射
数值 Enum 存在反向映射,即可以通过成员值获得成员名。
当我们把枚举打印出来,发现枚举的结构是双向的
enum Time {
Morning,
Afternoon,
Evening,
}
console.log(Time)
// 打印结果如下
{
'0': 'Morning',
'1': 'Afternoon',
'2': 'Evening',
Morning: 0,
Afternoon: 1,
Evening: 2
}所以也可以反向访问,即可以通过成员值获得成员名。
enum Time {
Morning,
Afternoon,
Evening,
}
console.log(Time[0]) // Morning
console.log(Time[1]) // Afternoon
console.log(Time[2]) // Evening这是因为 TypeScript 会将上面的 Enum 结构,编译成下面的 JavaScript 代码。
javascriptvar Time; (function (Time) { Time[(Time["Morning"] = 0)] = "Morning"; Time[(Time["Afternoon"] = 1)] = "Afternoon"; Time[(Time["Evening"] = 2)] = "Evening"; })(Time || (Time = {}));上面代码中,实际进行了两组赋值,以第一个成员为例。
javascriptTime[(Time["Morning"] = 0)] = "Morning";上面代码有两个赋值运算符(
=),实际上等同于下面的代码。javascriptTime["Morning"] = 0; Time[0] = "Morning";
WARNING
这种情况只发生在数值 Enum,对于字符串 Enum,不存在反向映射。这是因为字符串 Enum 编译后只有一组赋值。
enum MyEnum {
A = "a",
B = "b",
}
// 编译后
var MyEnum;
(function (MyEnum) {
MyEnum["A"] = "a";
MyEnum["B"] = "b";
})(MyEnum || (MyEnum = {}));可以通过打印结果看到
enum Time {
Morning = "早上",
Afternoon = "下午",
Evening = "晚上",
}
console.log(Time) // { Morning: '早上', Afternoon: '下午', Evening: '晚上' }
console.log(Time.Morning) // 早上
console.log(Time["早上"]) // 错误3.2.5 Enum作类型
Enum 结构本身也是一种类型。
enum Time {
Morning = '早上',
Afternoon = '下午',
Evening = '晚上',
}
const m: Time = Time.Morning
const a: Time = Time.Afternoon
const e: Time = Time.Evening
console.log(m) // 早上
console.log(a) // 下午
console.log(e) // 晚上定义的变量除了可以设置成Enum类型,也可以根据元素的值设置基本类型
enum Time {
Morning = 0,
Afternoon = '下午',
Evening = '晚上',
}
const m: Number = Time.Morning
const m1: Time = Time.Morning
const a: String = Time.Afternoon
const a1: Time = Time.AfternoonTIP
Enum 结构的特别之处在于,它既是一种类型,也是一个值。绝大多数 TypeScript 语法都是类型语法,编译后会全部去除,但是 Enum 结构是一个值,编译后会变成 JavaScript 对象,留在代码中。
// 编译前
enum Color {
Red, // 0
Green, // 1
Blue, // 2
}
// 编译后
let Color = {
Red: 0,
Green: 1,
Blue: 2,
};3.2.6 keyof运算符
keyof 运算符可以取出 Enum 结构的所有成员名,作为联合类型返回。
enum MyEnum {
A = "a",
B = "b",
}
// 'A'|'B'
type Foo = keyof typeof MyEnum;3.3 any
any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。
let x: any;
x = 1; // 正确
x = "foo"; // 正确
x = true; // 正确变量类型一旦设为any,TypeScript 实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。
let x: any = "hello";
x(1); // 不报错
x.foo = 100; // 不报错由于这个原因,应该尽量避免使用any类型,否则就失去了使用 TypeScript 的意义。
TIP
实际开发中,any类型主要适用以下两个场合。
- 出于特殊原因,需要关闭某些变量的类型检查,就可以把该变量的类型设为
any。 - 为了适配以前老的 JavaScript 项目,让代码快速迁移到 TypeScript,可以把变量类型设为
any。有些年代很久的大型 JavaScript 项目,尤其是别人的代码,很难为每一行适配正确的类型,这时你为那些类型复杂的变量加上any,TypeScript 编译时就不会报错。
3.3.1 类型推断
对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是any。
function add(x, y) {
return x + y;
}
add(1, [1, 2, 3]); // 不报错这显然是很糟糕的情况,所以对于那些类型不明显的变量,一定要显式声明类型,防止被推断为any。
3.3.2 污染问题
any类型除了关闭类型检查,还有一个很大的问题,就是它会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。
typescript
let x: any = "hello";
let y: number;
y = x; // 不报错
y * 123; // 不报错
y.toFixed(); // 不报错3.4 unknown
为了解决any类型“污染”其他变量的问题,TypeScript 3.0 引入了unknown类型。它与any含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像any那样自由,可以视为严格版的any。
unknown跟any的相似之处,在于所有类型的值都可以分配给unknown类型。
let x: unknown;
x = true; // 正确
x = 42; // 正确
x = "Hello World"; // 正确unknown类型跟any类型的不同之处在于,它不能直接使用。主要有以下几个限制。
首先,
unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)。tslet v: unknown = 123; let v1: boolean = v; // 报错 let v2: number = v; // 报错其次,不能直接调用
unknown类型变量的方法和属性。tslet v1: unknown = { foo: 123 }; v1.foo; // 报错 let v2: unknown = "hello"; v2.trim(); // 报错 let v3: unknown = (n = 0) => n + 1; v3(); // 报错再次,
unknown类型变量能够进行的运算是有限的,只能进行比较运算(运算符==、===、!=、!==、||、&&、?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错。tslet a: unknown = 1; a + 1; // 报错 a === 1; // 正确
想要使用unknown类型的变量,需要将类型缩小,所谓“类型缩小”,就是缩小unknown变量的类型范围,确保不会出错。
可以使用
typeoftslet a: unknown = 1; if (typeof a === "number") { let r = a + 10; // 正确 }也可以使用typeof
tslet n: unknown = 111 let num:number =n as number
3.5 never
为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。
由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。
let x: never;上面示例中,变量x的类型是never,就不可能赋给它任何值,否则都会报错。
never类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性,详见后面章节。另外,不可能返回值的函数,返回值的类型就可以写成never,详见《函数》一章。
如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never类型。
function fn(x: string | number) {
if (typeof x === "string") {
// ...
} else if (typeof x === "number") {
// ...
} else {
x; // never 类型
}
}上面示例中,参数变量x可能是字符串,也可能是数值,判断了这两种情况后,剩下的最后那个else分支里面,x就是never类型了。
never类型的一个重要特点是,可以赋值给任意其他类型。
function f(): never {
throw new Error("Error");
}
let v1: number = f(); // 不报错
let v2: string = f(); // 不报错
let v3: boolean = f(); // 不报错上面示例中,函数f()会抛错,所以返回值类型可以写成never,即不可能返回任何值。各种其他类型的变量都可以赋值为f()的运行结果(never类型)。
为什么never类型可以赋值给任意其他类型呢?这也跟集合论有关,空集是任何集合的子集。TypeScript 就相应规定,任何类型都包含了never类型。因此,never类型是任何其他类型所共有的,TypeScript 把这种情况称为“底层类型”(bottom type)。
总之,TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个。
4.联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。联合类型使用 | 分隔每个类型。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;