# 基本类型
# 概述
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'); // 报错
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不是对象,没有这个方法
2
只要打开strictNullChecks选项,undefined和null就只能赋值给自身,或any类型和unknown类型的变量,不能赋值给其他类型的变量。
# 值类型
TypeScript 规定,单个值也是一种类型,称为“值类型”。
let x:'hello';
//变量x的类型是字符串hello,导致它只能赋值为这个字符串,赋值为其他字符串就会报错。
x = 'hello'; // 正确
x = 'world'; // 报错
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 }
2
3
# 联合类型
指的是多个类型组成的一个新类型,使用符号|表示。表示取值可以为多种类型中的一种。
联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。
联合类型可以与值类型相结合,表示一个变量的值有若干种可能。
let x:string|number;
let rainbowColor:'赤'|'橙'|'黄'|'绿'|'青'|'蓝'|'紫';
2
前面提到,打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。
let name:string|null; // 变量name的值可以是字符串,也可以是null
name = 'John';
name = null;
2
3
“类型缩小”是 TypeScript 处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种类型放大,处理时就需要类型缩小。
function getPort(
scheme: 'http'|'https'
) {
switch (scheme) { //对参数变量scheme进行类型缩小,根据不同的值类型,返回不同的结果
case 'http':
return 80;
case 'https':
return 443;
}
}
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 的共有属性是没问题的
}
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
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';
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
2
3
4
同一段代码可能存在两种typeof运算符,一种用在值相关的 JavaScript 代码部分,另一种用在类型相关的 TypeScript 代码部分。它们遵守各自的规则,且编译后,前者会保留,后者会被全部删除。
由于编译时不会进行 JavaScript 的值运算,所以TypeScript 规定,typeof 的参数只能是标识符,不能是需要运算的表达式。
typeof命令的参数不能是类型,也不能是类型别名。
# 块级类型声明
类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效,在代码块外部无效。
# 类型的兼容
如果类型A的值可以赋值给类型B,那么类型A就称为类型B的子类型。
TypeScript 的一个规则是,凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行。因为子类型继承了父类型的所有特征,所以可以用在父类型的场合。但是,子类型还可能有一些父类型没有的特征,所以父类型不能用在子类型的场合。