类型系统

2024/4/8 TypeScript

# 基本类型

# 概述

​ JavaScript 语言(注意,不是 TypeScript)将值分成8种类型。

​ 分别是boolean、string、number、bigint、undefined、null、symbol、object。注意,上面所有类型的名称都是小写字母,首字母大写的Number、String、Boolean等在JavaScript语言中都是内置对象,而不是类型名称。另外,undefined和null既可作为值,也可作为类型,取决于在哪里使用它们。

​ TypeScript 继承了 JavaScript 的类型设计,以上8种类型可以看作 TypeScript 的基本类型,是类型系统的基础,复杂类型由它们组合而成。

# 简单介绍

  • boolean 类型:只包含true和false两个布尔值。
  • string 类型:包含所有字符串。
  • number 类型:包含所有整数和浮点数、非十进制数。
  • bigint 类型:包含所有的大整数。bigint与number类型不兼容。bigint类型赋值为整数和小数,都会报错。
  • symbol 类型:包含所有的 Symbol 值。
  • object 类型:包含了所有对象、数组和函数。
  • undefined 类型,null 类型:两种独立类型,它们各自都只有一个值。
    • undefined 类型只包含一个值undefined,表示未定义(即还未给出定义,以后可能会有定义)。
    • null 类型也只包含一个值null,表示为空(即此处没有值)。
    • 注意:如果没有声明类型的变量,被赋值为undefined或null,在关闭编译设置noImplicitAny和strictNullChecks时,它们的类型会被推断为any。如果希望避免这种情况,则需要打开编译选项strictNullChecks,打开后,赋值为undefined的变量会被推断为undefined类型,赋值为null的变量会被推断为null类型。

# 包装对象类型

# 包装对象的概念

​ JavaScript 的8种类型之中,undefined和null其实是两个特殊值,object属于复合类型,剩下的五种属于原始类型,代表最基本的、不可再分的值。

​ boolean、string、number、bigint、symbol,这五种原始类型的值,都有对应的包装对象。所谓包装对象,指的是这些值在需要时,会自动产生的对象。

  • symbol 类型和 bigint 类型无法直接获取它们的包装对象(即Symbol()和BigInt()不能作为构造函数使用),但是剩下三种可以。
    • 注意:Symbol和BigInt这两个类型虽然存在,但是完全没有使用的理由。目前在 TypeScript 里面,symbol和Symbol两种写法没有差异,bigint和BigInt也是如此。建议始终使用小写的symbol和bigint,不使用大写的Symbol和BigInt。
  • Boolean()、String()、Number()三个构造函数,执行后可直接获取某个原始类型值的包装对象。
    • 注意:String()只有当作构造函数使用时(带有new命令调用),才会返回包装对象。如果当作普通函数使用(不带有new命令),返回就是一个普通字符串。Number()和Boolean()也是如此。

# 包装对象类型与字面量类型

​ 由于包装对象的存在,导致每一个原始类型的值都有包装对象和字面量两种情况。

​ 为了区分这两种情况,TypeScript 对五种原始类型分别提供了大写和小写两种类型。

​ Boolean 和 boolean、String 和 string、Number 和 number、BigInt 和 bigint、Symbol 和 symbol。其中,大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象。建议只使用小写类型,不使用大写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。而且,TypeScript 把很多内置方法的参数,定义成小写类型,使用大写类型会报错。

'hello' // 字面量
new String('hello') // 包装对象
//String类型可以赋值为字符串的字面量,也可以赋值为包装对象。
const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确
//string类型只能赋值为字面量,赋值为包装对象就会报错。
const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 报错
1
2
3
4
5
6
7
8

# Object 类型与 object 类型

# Object 类型

​ 大写的Object类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值。

  • 原始类型值、对象、数组、函数都是合法的Object类型。
  • undefined和null赋值给Object类型,就会报错。
  • 空对象{}是Object类型的简写形式,所以使用Object时常常用空对象代替。

# object 类型

​ 小写的object类型代表 JavaScript 里面的狭义对象。即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值。

​ 大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。因此,建议总是使用小写类型object,不使用大写类型Object。

​ 注意:无论是大写的Object类型,还是小写的object类型,都只包含 JavaScript 内置对象原生的属性和方法,用户自定义的属性和方法都不存在于这两个类型之中。

# undefined 和 null 的特殊性

​ undefined和null既是值,又是类型。

​ 作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为undefined或null。以便跟 JavaScript 的行为保持一致(变量如果等于undefined就表示还没有赋值,如果等于null就表示值为空)。

​ 但是有时候,这并不是开发者想要的行为,也不利于发挥类型系统的优势。

const obj:object = undefined;
obj.toString() // 编译不报错,运行就报错。因为undefined不是对象,没有这个方法
1
2

​ 只要打开strictNullChecks选项,undefined和null就只能赋值给自身,或any类型和unknown类型的变量,不能赋值给其他类型的变量。

# 值类型

​ TypeScript 规定,单个值也是一种类型,称为“值类型”。

let x:'hello';
//变量x的类型是字符串hello,导致它只能赋值为这个字符串,赋值为其他字符串就会报错。
x = 'hello'; // 正确
x = 'world'; // 报错
1
2
3
4

​ 推断类型时,遇到const命令声明的变量,如果代码里没有注明类型,就会推断该变量是值类型。const命令声明的变量,一旦声明就不能改变,相当于常量。值类型就意味着不能赋为其他值。

​ const命令声明的变量,如果赋值为对象,并不会推断为值类型。const变量赋值为对象时,属性值是可以改变的。

​ 只包含单个值的值类型,用处不大。实际开发中,往往将多个值结合,作为联合类型使用。

const x = 'https'; // x 的类型是 "https"
const y:string = 'https'; // y 的类型是 string
const x = { foo: 1 }; // x 的类型是 { foo: number }
1
2
3

# 联合类型

​ 指的是多个类型组成的一个新类型,使用符号|表示。表示取值可以为多种类型中的一种。

​ 联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。

​ 联合类型可以与值类型相结合,表示一个变量的值有若干种可能。

let x:string|number;
let rainbowColor:'赤'|'橙'|'黄'|'绿'|'青'|'蓝'|'紫';
1
2

​ 前面提到,打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。

let name:string|null; // 变量name的值可以是字符串,也可以是null
name = 'John';
name = null;
1
2
3

​ “类型缩小”是 TypeScript 处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种类型放大,处理时就需要类型缩小。

function getPort(
  scheme: 'http'|'https'
) {
  switch (scheme) { //对参数变量scheme进行类型缩小,根据不同的值类型,返回不同的结果
    case 'http':
      return 80;
    case 'https':
      return 443;
  }
}
1
2
3
4
5
6
7
8
9
10

​ 当TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型。

function getLength(something: string | number): number {
    return something.length; // length 不是 string 和 number 的共有属性,所以会报错
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.
function getString(something: string | number): string {
    return something.toString(); // 访问 string 和 number 的共有属性是没问题的
}
1
2
3
4
5
6
7
8

# 交叉类型

​ 指的多个类型组成的一个新类型,使用符号&表示。

​ 交叉类型A&B表示,任何一个类型必须同时属于A和B,才属于交叉类型A&B,即交叉类型同时满足A和B的特征。

​ 交叉类型的主要用途是表示对象的合成。常常用来为对象类型添加新属性。

type A = { foo: number };
type B = A & { bar: number }; // 类型B是一个交叉类型,用来在A的基础上增加了属性bar
1
2

# type 命令

​ 用来定义一个类型的别名。别名可以让类型的名字变得更有意义,也能增加代码的可读性,还可以使复杂类型用起来更方便,便于以后修改变量的类型。类型别名常用于联合类型。

​ 别名不允许重名。

​ 别名的作用域是块级作用域。这意味着,代码块内部定义的别名,影响不到外部。

​ 别名支持使用表达式,也可以在定义一个别名时,使用另一个别名,即别名允许嵌套。

​ type命令属于类型相关的代码,编译成 JavaScript 的时候,会被全部删除。

​ 类型别名与字符串字面量类型都是使用 type 进行定义。

type Age = number; // 为number类型定义了一个别名Age
let age:Age = 55; // 像使用number一样,使用Age作为类型
type World = "world";
type Greeting = `hello ${World}`; // 使用了模板字符串,读取另一个别名World
// 使用 type 定了一个字符串字面量类型EventNames,它只能取三种字符串中的一种
type EventNames = 'click' | 'scroll' | 'mousemove';
1
2
3
4
5
6

# typeof 运算符

​ JavaScript中,typeof 运算符是一个一元运算符,返回一个字符串,代表操作数的类型。它的操作数是一个值,typeof运算符只可能返回八种结果,而且都是字符串。

​ TypeScript 将typeof运算符移植到了类型运算,它的操作数依然是一个值,但是返回的不是字符串,而是该值的 TypeScript 类型。所以只能用在类型运算中(跟类型相关的代码中),不能用在值运算。

typeof 'foo'; // 'string'
const a = { x: 0 };
type T0 = typeof a;   // { x: number }
type T1 = typeof a.x; // number
1
2
3
4

​ 同一段代码可能存在两种typeof运算符,一种用在值相关的 JavaScript 代码部分,另一种用在类型相关的 TypeScript 代码部分。它们遵守各自的规则,且编译后,前者会保留,后者会被全部删除。

​ 由于编译时不会进行 JavaScript 的值运算,所以TypeScript 规定,typeof 的参数只能是标识符,不能是需要运算的表达式。

​ typeof命令的参数不能是类型,也不能是类型别名。

# 块级类型声明

​ 类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效,在代码块外部无效。

# 类型的兼容

​ 如果类型A的值可以赋值给类型B,那么类型A就称为类型B的子类型。

​ TypeScript 的一个规则是,凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行。因为子类型继承了父类型的所有特征,所以可以用在父类型的场合。但是,子类型还可能有一些父类型没有的特征,所以父类型不能用在子类型的场合。

上次更新: 2024/4/13 07:03:33