外观
外观
怡然
3580字约12分钟
2024-08-21
any
类型any
类型表示没有任何限制,该类型的变量可以赋予任意类型的值。any
,TypeScript
实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。let x: any;
x = 1; // 正确
x = "foo"; // 正确
x = true; // 正确
TypeScript
必须自己推断类型的那些变量,如果无法推断出类型,TypeScript
就会认为该变量的类型是any
。TypeScript
提供了一个编译选项noImplicitAny
,打开该选项,只要推断出any
类型就会报错。noImplicitAny
,使用let
和var
命令声明变量,但不赋值也不指定类型,是不会报错的。由于这个原因,建议使用let
和var
声明变量时,如果不赋值,就一定要显式声明类型,否则可能存在安全隐患。const
命令没有这个问题,因为 JavaScript
语言规定const
声明变量时,必须同时进行初始化(赋值)。any
类型除了关闭类型检查,还会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。unknown
类型unknown
跟any
的相似之处,在于所有类型的值都可以分配给unknown
类型。let x: unknown;
x = true; // 正确
x = 42; // 正确
x = "Hello World"; // 正确
unknown
类型的变量,不能直接赋值给其他类型的变量(除了any
类型和unknown
类型)。let v: unknown = 123;
let v1: boolean = v; // 报错
let v2: number = v; // 报错
unknown
类型变量的方法和属性。let v1: unknown = { foo: 123 };
v1.foo; // 报错
let v2: unknown = "hello";
v2.trim(); // 报错
let v3: unknown = (n = 0) => n + 1;
v3(); // 报错
unknown
类型变量能够进行的运算是有限的,只能进行比较运算(运算符==
、===
、!=
、!==
、||
、&&
、?
)、取反运算(运算符!
)、typeof
运算符和instanceof
运算符这几种,其他运算都会报错。let a: unknown = 1;
a + 1; // 报错
a === 1; // 正确
unknown
类型变量通过类型缩小,才可以使用。let a: unknown = 1;
if (typeof a === "number") {
let r = a + 10; // 正确
}
unknown
可以看作是更安全的any
。一般来说,凡是需要设为any
类型的地方,通常都应该优先考虑设为unknown
类型。在集合论上,unknown
也可以视为所有其他类型(除了any
)的全集,所以它和any
一样,也属于 TypeScript
的顶层类型。never
类型TypeScript
还引入了“空类型”的概念,即该类型为空,不包含任何值。由于不存在任何属于“空类型”的值,所以该类型被称为never
,即不可能有这样的值。never
类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性。另外,不可能返回值的函数,返回值的类型就可以写成never
。如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never
类型。function fn(x: string | number) {
if (typeof x === "string") {
// ...
} else if (typeof x === "number") {
// ...
} else {
x; // never 类型
}
}
never
类型的一个重要特点是,可以赋值给任意其他类型。function f(): never {
throw new Error("Error");
}
let v1: number = f(); // 不报错
let v2: string = f(); // 不报错
let v3: boolean = f(); // 不报错
TypeScript
就相应规定,任何类型都包含了never
类型。因此,never
类型是任何其他类型所共有的,TypeScript
把这种情况称为“底层类型”(bottom type
)。相关信息
总之,TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个。
boolean
类型const x: boolean = true;
const y: boolean = false;
string
类型const x: string = "hello";
const y: string = `${x} world`;
number
类型const x: number = 123;
const y: number = 3.14;
const z: number = 0xffff;
bigint
类型const x: bigint = 123n;
const y: bigint = 0xffffn;
bigint
类型和number
类型不兼容symbol
类型const x: symbol = Symbol();
object
类型object
类型包含了所有对象、数组和函数。const x: object = { foo: 123 };
const y: object = [1, 2, 3];
const z: object = (n: number) => n + 1;
undefined
类型,null
类型undefined
和null
既是值,又是类型。undefined
或null
。JavaScript
的行为是,变量如果等于undefined
就表示还没有赋值,如果等于null
就表示值为空。所以,TypeScript 就允许了任何类型的变量都可以赋值为这两个值。let x: undefined = undefined;
const x: null = null;
undefined
或null
,它们的类型会被推断为any
。TypeScript
提供了一个编译选项strictNullChecks
。只要打开这个选项,undefined
和null
就不能赋值给其他类型的变量(除了any类型和unknown类型)。strictNullChecks
以后,undefined
和null
这两种值也不能互相赋值了。let a = undefined; // any
const b = undefined; // any
let c = null; // any
const d = null; // any
// 打开编译设置 strictNullChecks
let a = undefined; // undefined
const b = undefined; // undefined
let c = null; // null
const d = null; // null
JavaScript
的 8 种类型之中,undefined
和null
其实是两个特殊值,object
属于复合类型,剩下的五种属于原始类型(primitive value),代表最基本的、不可再分的值(boolean
、string
、number
、bigint
、symbol
)。五种原始类型的值,都有对应的包装对象(wrapper object)。所谓“包装对象”,指的是这些值在需要时,会自动产生的对象。
"hello".charAt(1); // 'e'
hello
执行了charAt()
方法。但是,在 JavaScript
语言中,只有对象才有方法,原始类型的值本身没有方法。这行代码之所以可以运行,就是因为在调用方法时,字符串会自动转为包装对象,charAt()
方法其实是定义在包装对象上。这样的设计大大方便了字符串处理,省去了将原始类型的值手动转成对象实例的麻烦。
- 五种包装对象之中,
symbol
类型和bigint
类型无法直接获取它们的包装对象(即Symbol()
和BigInt()
不能作为构造函数使用),但是剩下三种可以(boolean
、string
、number
)。
const s = new String("hello");
typeof s; // 'object'
s.charAt(1); // 'e'
hello
的包装对象,typeof
运算符返回object
,不是string
,但是本质上它还是字符串,可以使用所有的字符串方法。
String()
只有当作构造函数使用时(即带有new
命令调用),才会返回包装对象。如果当作普通函数使用(不带有new
命令),返回就是一个普通字符串。其他两个构造函数Number()
和Boolean()
也是如此。
由于包装对象的存在,导致每一个原始类型的值都有包装对象和字面量两种情况。
"hello"; // 字面量
new String("hello"); // 包装对象
为了区分这两种情况,TypeScript 对五种原始类型分别提供了大写和小写两种类型(
Boolean 和 boolean
,String 和 string
,Number 和 number
,BigInt 和 bigint
,Symbol 和 symbol
)。其中,大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象。
const s1: String = "hello"; // 正确
const s2: String = new String("hello"); // 正确
const s3: string = "hello"; // 正确
const s4: string = new String("hello"); // 报错
Object
类型与 object
类型Object
类型Object
类型代表 JavaScript
语言里面的广义对象。所有可以转成对象的值,都是Object
类型,这囊括了几乎所有的值。let obj: Object;
obj = true;
obj = "hi";
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
undefined
和null
这两个值不能转为对象,其他任何值都可以赋值给Object
类型。let obj: Object;
obj = undefined; // 报错
obj = null; // 报错
{}
是Object
类型的简写形式,所以使用Object
时常常用空对象代替。let obj: {};
obj = true;
obj = "hi";
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
object
类型object
类型代表 JavaScript
里面的狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值。let obj: object;
obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
obj = true; // 报错
obj = "hi"; // 报错
obj = 1; // 报错
const o1: Object = { foo: 0 };
const o2: object = { foo: 0 };
o1.toString(); // 正确
o1.foo; // 报错
o2.toString(); // 正确
o2.foo; // 报错
toString()
是对象的原生方法,可以正确访问。foo
是自定义属性,访问就会报错。undefined
和null
的特殊性
undefined
和null
作为值有一个特殊的地方,任何其他类型的变量都可以赋值为undefined
和null
。JavaScript
的行为是,变量如果等于undefined
就表示还没有赋值,如果等于null
就表示值为空。所以,TypeScript
就允许了任何类型的变量都可以赋值为这两个值。
let age: number = 24;
age = null; // 正确
age = undefined; // 正确
TypeScript
提供了一个编译选项strictNullChecks
。只要打开这个选项,undefined
和null
就不能赋值给其他类型的变量(除了any
类型和unknown
类型)。- 打开
strictNullChecks
以后,undefined
和null
这两种值也不能互相赋值了。
{
"compilerOptions": {
"strictNullChecks": true
// ...
}
}
TypeScript
规定,单个值也是一种类型,称为“值类型”。TypeScript
推断类型时,遇到const
命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。let x: "hello";
x = "hello"; // 正确
x = "world"; // 报错
// x 的类型是 "https"
const x = "https";
// y 的类型是 string
const y: string = "https";
const
命令声明的变量,如果赋值为对象,并不会推断为值类型。// x 的类型是 { foo: number }
const x = { foo: 1 };
let x: 5 = 5; // x属于子类型
let y: number = 4 + 1; // y属于父类型
x = y; // 报错
y = x; // 正确
|
|表示。let x: string | number;
x = 123; // 正确
x = "abc"; // 正确
// 上面示例中,变量x就是联合类型string|number,表示它的值既可以是字符串,也可以是数值。
let setting: true | false;
let gender: "male" | "female";
let rainbowColor: "赤" | "橙" | "黄" | "绿" | "青" | "蓝" | "紫";
strictNullChecks
后,其他类型的变量不能赋值为undefined
或null
。这时,如果某个变量确实可能存在空值,可以采用联合类型的写法。let name: string | null;
name = "John";
name = null;
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
&
表示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",
};
type A = { foo: number };
type B = A & { bar: number };
type
命令type
命令用来定义一个类型的别名。type Age = number;
let age: Age = 55;
// 使用Age作为number类型,增加代码可读性,使复杂类型使用更方便,以便于修改变量类型。
type Color = "red";
if (Math.random() < 0.5) {
type Color = "blue";
}
type World = "world";
type Greeting = `hello ${World}`;
typeof
运算符TypeScript
中的typeof
运算符,返回的不是字符串,而是该值的TypeScript
类型。const a = { x: 0 };
type T0 = typeof a; // { x: number } 返回变量a的 TypeScript 类型
type T1 = typeof a.x; // number 返回属性x的类型
JavaScript
的值运算,所以 TypeScript
规定,typeof
的参数只能是标识符,不能是需要运算的表达式。type T = typeof Date(); // 报错
typeof
命令的参数不能是类型。type Age = number;
type MyAge = typeof Age; // 报错
TypeScript
支持块级类型声明,即类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效。if (true) {
type T = number;
let v: T = 5;
} else {
type T = string;
let v: T = "hello";
}
TypeScript
的类型存在兼容关系,某些类型可以兼容其他类型。A
的值可以赋值给类型B
,那么类型A
就称为类型B
的子类型(subtype
)。type T = number | string;
let a: number = 1;
let b: T = a;
// 变量a和b的类型是不一样的,但是变量a赋值给变量b并不会报错。b的类型兼容a的类型。
let a: "hi" = "hi";
let b: string = "hello";
b = a; // 正确
a = b; // 报错
// hi是string的子类型,string是hi的父类型。所以,变量a可以赋值给变量b,但是反过来就会报错。