# 基础类型 (8 种)

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

  • boolean 布尔
  • string 字符串
  • number 数量
  • bigint 长整型数字
  • symbol 象征
  • object 对象
  • undefined 未定义的
  • null 零值

TypeScript 继承了 JavaScript 的类型设计,以上 8种类型 可以看作 TypeScript 的基本类型。
注意,上面所有类型的名称都是小写字母,首字母大写的 Number、String、Boolean 等在 JavaScript 语言中都是内置对象,而不是类型名称。

  1. 布尔值
let isDone: boolean = false;
  1. 字符串

(普通字符串和模板字符串都属于 string 类型)

let str1: string = "bob";
let str2: string = `${str1} world`;
  1. number 类型
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
  1. 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; // 报错
  1. symbol 类型

symbol 类型包含所有的 Symbol 值。

const x:symbol = Symbol();
  1. 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];
  1. undefined
  2. null
let u: undefined = undefined;
let n: null = null;

注意,如果没有声明类型的变量,被赋值为 undefinednull ,在关闭编译设置 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

TsypeScriptreadonly 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 。由于 5number 的子类型, number5 的父类型,父类型不能赋值给子类型,所以报错了.
但是,反过来是可以的,子类型可以赋值给父类型。

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 表示,任何一个类型只要属于 AB ,就属于联合类型 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 后,其他类型的变量不能赋值为 undefinednul 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 表示,任何一个类型必须同时属于 AB ,才属于交叉类型 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; // 报错

上面示例中, histring 的子类型, stringhi 的父类型。所以,变量 a 可以赋值给变量 b ,但是反过来就会报错。