📚 TypeScript 教程系列

  1. 入门与配置
  2. 基础类型与变量声明(本文)
  3. 函数
  4. 流程控制与运算符
  5. 集合类型
  6. 异步编程与错误处理
  7. 接口与类
  8. 泛型与类型组合
  9. 高级类型
  10. 模块、装饰器与工程化

⚠️ 来源声明:本文内容参考自 菜鸟教程 TypeScript 教程,仅供学习交流,版权归原作者所有。
TypeScript 的核心价值在于静态类型系统,它让 JavaScript 在编译阶段就能捕获大量潜在错误。本章将系统梳理 TypeScript 的基础类型体系、变量声明方式,以及 null/undefined、Symbol、Number、String 等原始类型的深入用法,并讲解类型推断与类型断言这两项让类型系统既灵活又可控的关键机制。

基础类型

基础类型可以让开发者更准确地描述数据的结构和意图。TypeScript 包含的数据类型如下表所示:

类型描述示例
string表示文本数据let name: string = "Alice";
number表示数字,包括整数和浮点数let age: number = 30;
boolean表示布尔值 truefalselet isDone: boolean = true;
array表示相同类型的元素数组let list: number[] = [1, 2, 3];
tuple表示已知类型和长度的数组let person: [string, number] = ["Alice", 30];
enum定义一组命名常量enum Color { Red, Green, Blue };
any任意类型,不进行类型检查let value: any = 42;
void无返回值(常用于函数)function log(): void {}
null表示空值let empty: null = null;
undefined表示未定义let undef: undefined = undefined;
never表示不会有返回值function error(): never { throw new Error("error"); }
object表示非原始类型let obj: object = { name: "Alice" };
union联合类型,表示可以是多种类型之一let id: string | number;
unknown不确定类型,需类型检查后再使用let value: unknown = "Hello";

需要注意的是,TypeScript 和 JavaScript 都没有独立的整数类型,所有数字统一用 number 表示。

类型如何映射到 JavaScript

TypeScript 的类型信息只存在于编译期:编译为 JavaScript 后,几乎所有类型注解都会被完全删除,运行时只剩下普通的 JS 值。这意味着类型检查无法在运行时生效,类型错误只能在编译阶段被发现。各类型编译后的去向如下:

类型编译后运行时表现
stringnumberboolean删除类型注解普通 JS 字符串 / 数字 / 布尔值(number 无整数类型,均为双精度浮点数)
arraytuple删除类型注解普通 JS 数组,元素类型与长度约束完全失效
object删除类型注解普通 JS 对象
nullundefined删除类型约束JS 原生值本身,strictNullChecks 仅编译期生效
anyunknown删除类型注解无类型的 JS 值;any 绕过检查,unknown 的检查仅编译期有效
voidnever删除类型注解void 函数正常返回 undefinednever 函数正常执行(抛异常或死循环)
联合类型、字面量类型删除类型注解普通 JS 值,类型信息完全消失
symbol删除类型注解Symbol() 创建的唯一值
enum生成运行时代码编译为 IIFE 生成双向映射对象(Color.Red === 0Color[0] === "Red");const enum 则内联为常量,不生成对象
NumberString(包装对象)删除类型注解new Number() / new String() 创建的对象实例,typeof"object"
类型推断、类型断言纯编译期机制不产生任何运行时代码,断言不改变运行时类型

核心原则:enum 会生成运行时对象外,TypeScript 的所有类型——注解、推断、断言——都是编译期的"幻影",编译后即消失,运行时只剩 JavaScript 本身。正因如此,类型断言并非类型转换:"42" as number 在运行时仍是字符串,真正的转换需借助 Number()String() 等函数。

string 字符串

string 表示文本数据,只能存储字符串,通常用于描述文字信息。

1
let message: string = "Hello, TypeScript!";

TypeScript 支持模板字符串,用反引号 `(注意不是单引号 ')来定义,允许在字符串中插入变量或表达式,非常适合多行文本和拼接变量。

1
2
3
let name: string = "Alice";
let greeting: string = `Hello, ${name}! Welcome to TypeScript.`;
console.log(greeting); // 输出:Hello, Alice! Welcome to TypeScript.

字符串字面量也支持多行书写与内嵌表达式:

1
2
3
4
5
let name: string = "Gene";
let age: number = 37;
let sentence: string = `Hello, my name is ${name}.

I'll be ${age + 1} years old next month.`;

number 数字

TypeScript 使用 number 表示所有数字,包括整数、浮点数,以及二进制、八进制、十六进制字面量。

1
2
3
4
5
6
7
let age: number = 25;
let temperature: number = 36.5;

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

boolean 布尔值

boolean 表示逻辑值 truefalse,用于条件判断。

1
2
let isCompleted: boolean = false;
let isDone: boolean = false;

array 数组

数组可以表示一组相同类型的元素,有两种声明方式:type[]Array<type>(数组泛型)。

1
2
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];

tuple 元组

元组表示已知数量和类型的数组,每个元素可以是不同的类型,适合表示固定结构的数据。

1
2
3
4
let person: [string, number] = ["Alice", 25];

let x: [string, number];
x = ['hello', 10];

enum 枚举

enum 用来定义一组命名常量,是对 JavaScript 标准数据类型的一个补充。默认情况下枚举的值从 0 开始递增。

1
2
3
4
5
6
enum Color {
Red,
Green,
Blue,
}
let favoriteColor: Color = Color.Green;

也可以手动指定成员的数值,未显式赋值的成员会接着前一个的值递增:

1
2
3
4
5
6
enum Color { Red = 1, Green, Blue }
let c: Color = Color.Green;

// 全部手动赋值
enum Color2 { Red = 1, Green = 2, Blue = 4 }
let c2: Color2 = Color2.Green;

枚举类型提供的一个便利是可以由枚举的值反查它的名字:

1
2
3
enum Color { Red = 1, Green, Blue }
let colorName: string = Color[2];
console.log(colorName); // 显示 'Green',因为它的值是 2

如果一个枚举成员的值是被计算出来的(例如调用函数),那么它后面一位的成员必须显式初始化值,否则编译不通过:

1
2
3
4
5
6
7
8
9
10
11
12
const getValue = () => {
return 0;
};

enum List {
A = getValue(),
B = 2, // 此处必须要初始化值,不然编译不通过
C,
}
console.log(List.A); // 0
console.log(List.B); // 2
console.log(List.C); // 3

any 类型

any 可以表示任何类型,适合不确定数据类型的情况。但使用时需谨慎,因为 any 会绕过类型检查。

1
2
let randomValue: any = 42;
randomValue = "hello";

任意值是 TypeScript 针对编程时类型不明确的变量使用的一种数据类型,常用于以下三种情况。

1. 变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查:

1
2
3
let x: any = 1;        // 数字类型
x = 'I am who I am'; // 字符串类型
x = false; // 布尔类型

2. 改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查:

1
2
3
let x: any = 4;
x.ifItExists(); // 正确,ifItExists 方法在运行时可能存在,但这里并不会检查
x.toFixed(); // 正确

3. 定义存储各种类型数据的数组时

1
2
let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;

void 空类型

void 用于没有返回值的函数。声明变量时,类型 void 意味着只能赋值 nullundefined

1
2
3
function logMessage(message: string): void {
console.log(message);
}

null 和 undefined

nullundefined 分别表示"空值"和"未定义"。在 JavaScript 中:

  • null 表示"什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,用 typeof 检测 null 返回的是 object
  • undefined 表示一个没有设置值的变量,typeof 一个没有值的变量会返回 undefined

在默认情况下,nullundefined 是所有类型(包括 void)的子类型,可以赋值给其它类型。而启用 --strictNullChecks 严格空校验后,它们只能被赋值给 void 或本身对应的类型:

1
2
3
4
5
// 启用 --strictNullChecks
let x: number;
x = 1; // 编译正确
x = undefined; // 编译错误
x = null; // 编译错误

如果一个类型可能出现 nullundefined,可以用 | 联合类型来支持多种类型:

1
2
3
4
5
// 启用 --strictNullChecks
let x: number | null | undefined;
x = 1; // 编译正确
x = undefined; // 编译正确
x = null; // 编译正确
1
2
let empty: null = null;
let notAssigned: undefined = undefined;

never 类型

never 表示不会有返回值,通常用于抛出错误或进入无限循环的函数,表示该函数永远不会正常结束。never 是其它类型(包括 nullundefined)的子类型,代表从不会出现的值。

1
2
3
4
5
6
7
function throwError(message: string): never {
throw new Error(message);
}

function loop(): never {
while (true) {}
}

声明为 never 类型的变量只能被 never 类型所赋值。在函数中它通常表现为抛出异常或无法执行到终止点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let x: never;
let y: number;

// 编译错误,数字类型不能转为 never 类型
x = 123;

// 运行正确,never 类型可以赋值给 never 类型
x = (() => { throw new Error('exception') })();

// 运行正确,never 类型可以赋值给数字类型
y = (() => { throw new Error('exception') })();

// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
}

// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}

object 对象类型

object 表示非原始类型的值,适用于复杂的对象结构。

1
let person: object = { name: "Alice", age: 30 };

联合类型

联合类型表示一个变量可以是多种类型之一,通过 | 符号实现。

1
2
3
let id: string | number;
id = "123";
id = 456;

unknown 不确定类型

unknownany 类似,但更严格。必须经过类型检查后才能赋值给其他类型变量。

1
2
3
4
let value: unknown = "Hello";
if (typeof value === "string") {
let message: string = value;
}

字面量类型

字面量类型可以让变量只能拥有特定的值,常结合联合类型定义变量的特定状态。

1
2
let direction: "up" | "down" | "left" | "right";
direction = "up";

综合实例

以下实例展示了 TypeScript 中主要基础类型的定义和使用,模拟一个用户对象和相关的操作函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 定义枚举类型,用于表示用户的角色
enum Role {
Admin,
User,
Guest,
}

// 使用 interface 定义用户的结构
interface User {
id: number; // number 类型,用于唯一标识用户
username: string; // string 类型,表示用户名
isActive: boolean; // boolean 类型,表示用户是否激活
role: Role; // enum 类型,用于表示用户角色
hobbies: string[]; // array 类型,存储用户的兴趣爱好
contactInfo: [string, number]; // tuple 类型,包含电话号码的元组
}

// 创建用户对象,符合 User 接口的结构
const user: User = {
id: 1,
username: "Alice",
isActive: true,
role: Role.User,
hobbies: ["Reading", "Gaming"],
contactInfo: ["+1", 123456789],
};

// 定义一个返回字符串的函数来获取用户信息
function getUserInfo(user: User): string {
return `User ${user.username} is ${user.isActive ? "active" : "inactive"} with role ${Role[user.role]}`;
}

// 使用 void 类型定义一个函数,专门打印用户信息
function printUserInfo(user: User): void {
console.log(getUserInfo(user));
}

// 定义一个 union 类型的函数参数,接受用户 ID 或用户名
function findUser(query: number | string): User | undefined {
if (typeof query === "number") {
return query === user.id ? user : undefined;
} else if (typeof query === "string") {
return query === user.username ? user : undefined;
}
return undefined;
}

// 定义一个 never 类型的函数,用于处理程序的异常情况
function throwError(message: string): never {
throw new Error(message);
}

// 使用 any 类型处理未知类型的数据
let unknownData: any = "This is a string";
unknownData = 42; // 重新赋值为数字,类型为 any

// 使用 unknown 类型处理不确定的数据,更加安全
let someData: unknown = "Possible data";
if (typeof someData === "string") {
console.log(`Length of data: ${(someData as string).length}`);
}

// 调用各个函数并测试
printUserInfo(user); // 打印用户信息
console.log(findUser(1)); // 根据 ID 查找用户
console.log(findUser("Alice")); // 根据用户名查找用户

// 使用 null 和 undefined 类型的变量
let emptyValue: null = null;
let uninitializedValue: undefined = undefined;

说明:

  • 枚举类型 (Role):用于定义用户角色的命名常量 AdminUserGuest
  • 接口 (User):定义了 User 对象的结构,展示了 stringnumberbooleanarraytuple 类型的使用。
  • 函数 getUserInfo:返回用户的描述信息,使用了字符串插值并结合 enum
  • 函数 printUserInfo:类型为 void,因为它仅输出信息,不返回任何值。
  • 联合类型 (number | string):用于 findUser 函数的参数,可以接受数字或字符串。
  • never 类型throwError 函数抛出错误,不会正常返回。
  • any 类型:展示如何声明一个可以接收任何类型的变量。
  • unknown 类型:类似 any,但更加安全,示例中在类型断言之前进行类型检查。

变量声明

变量是一种使用方便的占位符,用于引用计算机内存地址,就像存放数据的容器。TypeScript 遵循强类型语法,声明变量时需要指定类型。

变量命名规则

  • 名称可以包含数字和字母。
  • 除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。
  • 名称不能以数字开头。

四种声明方式

TypeScript 变量声明主要有以下四种形式:

1. 声明变量的类型及初始值:

1
var [变量名] : [类型] = 值;

例如:var uname:string = "Runoob";

2. 声明变量的类型,但没有初始值,变量值会设置为 undefined

1
var [变量名] : [类型];

例如:var uname:string;

3. 声明变量并初始值,但不设置类型,该变量可以是任意类型:

1
var [变量名] = 值;

例如:var uname = "Runoob";

4. 声明变量既没有类型,也没有初始值,该变量可以是任意类型,默认值为 undefined

1
var [变量名];

例如:var uname;

完整示例

1
2
3
4
5
6
7
8
var uname: string = "Runoob";
var score1: number = 50;
var score2: number = 42.50;
var sum = score1 + score2;
console.log("名字: " + uname);
console.log("第一个科目成绩: " + score1);
console.log("第二个科目成绩: " + score2);
console.log("总成绩: " + sum);

编译后的 JavaScript:

1
2
3
4
5
6
7
8
var uname = "Runoob";
var score1 = 50;
var score2 = 42.50;
var sum = score1 + score2;
console.log("名字: " + uname);
console.log("第一个科目成绩: " + score1);
console.log("第二个科目成绩: " + score2);
console.log("总成绩: " + sum);

输出结果:

1
2
3
4
名字: Runoob
第一个科目成绩: 50
第二个科目成绩: 42.5
总成绩: 92.5

注意: 避免使用 name 作为变量名,因为它会与 DOM 中的全局 window.name 属性冲突。

TypeScript 遵循强类型语法,类型不匹配会导致编译错误:

1
var num: number = "hello";     // 这个代码会编译错误

let 与 const

除了 var,TypeScript 还支持 letconst

  • var:函数作用域,存在变量提升,容易导致意外行为。
  • let:块级作用域,不存在变量提升,同一作用域内不能重复声明。
  • const:块级作用域,声明时必须初始化,且不可重新赋值(但对象属性仍可修改)。
1
2
3
4
5
6
7
8
9
let count: number = 10;
count = 20; // 允许

const pi: number = 3.14159;
// pi = 3.14; // 错误:不能给常量重新赋值

const config = { host: "localhost" };
config.host = "127.0.0.1"; // 允许:修改对象属性
// config = {}; // 错误:不能重新赋值

解构声明

TypeScript 支持数组解构和对象解构,方便从数组或对象中提取值:

1
2
3
4
5
6
7
8
9
// 数组解构
let [first, second] = [10, 20];
console.log(first); // 10
console.log(second); // 20

// 对象解构
let { name, age } = { name: "Alice", age: 30 };
console.log(name); // Alice
console.log(age); // 30

变量作用域

变量作用域决定了变量在哪里被定义和访问。TypeScript 有三种作用域:

  • 全局作用域:全局变量定义在程序结构之外,可以在代码的任何地方访问。
  • 类作用域:也称为字段,在类内部声明但在方法外部。可以通过类的对象访问。静态变量通过类名直接访问。
  • 局部作用域:局部变量只能在声明它们的代码块(如方法)内部使用。
1
2
3
4
5
6
7
8
9
10
11
12
var global_num = 12;             // 全局变量
class Numbers {
num_val = 13; // 实例变量
static sval = 10; // 静态变量
storeNum(): void {
var local_num = 14; // 局部变量
}
}
console.log("全局变量为: " + global_num);
console.log(Numbers.sval); // 静态变量
var obj = new Numbers();
console.log("实例变量: " + obj.num_val);

编译后的 JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var global_num = 12;
var Numbers = /** @class */ (function () {
function Numbers() {
this.num_val = 13;
}
Numbers.prototype.storeNum = function () {
var local_num = 14;
};
Numbers.sval = 10;
return Numbers;
}());
console.log("全局变量为: " + global_num);
console.log(Numbers.sval);
var obj = new Numbers();
console.log("实例变量: " + obj.num_val);

输出结果:

1
2
3
全局变量为: 12
10
实例变量: 13

在方法外访问 local_num 会报错:error TS2322: Could not find symbol 'local_num'.

特殊类型

TypeScript 的类型系统除了常规类型外,还提供了 nevervoidunknownany 四种特殊类型处理特定场景。理解它们的区别有助于编写更安全、更准确的代码。

四种特殊类型对比

类型描述特点
any任意类型绕过类型检查,无安全性
unknown安全任意类型使用需检查,有安全性
void无返回值用于函数,可返回 undefined
never永不返回抛出异常/循环,所有类型的子类型

类型安全等级从低到高为:any(最低)→ unknownvoidnever(最高,最严格)。

never 类型

never 表示永不出现的值,用于函数抛出异常或运行无限循环。never 是所有类型的子类型,这意味着 never 可以赋值给任何类型,但任何类型都不能赋值给 never(除了 never 本身)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 抛出异常的函数
function throwError(message: string): never {
throw new Error(message);
}

// 无限循环的函数
function infiniteLoop(): never {
while (true) {
console.log("运行中...");
}
}

// never 是所有类型的子类型
var neverValue: never;
var num: number = neverValue;
console.log("never 赋值给 number: " + num);

运行结果:never 赋值给 number: undefined

void 类型

void 表示没有返回值,用于没有 return 语句的函数。void 实际上和 undefined 很相似,主要用于函数的返回类型声明。

1
2
3
4
5
6
7
8
function logMessage(message: string): void {
console.log("日志: " + message);
}

logMessage("Hello");

var empty: void = undefined;
console.log("void 变量: " + empty);

运行结果:

1
2
日志: Hello
void 变量: undefined

unknown 类型

unknownany 的类型安全版本。使用前必须进行类型检查,这保护了类型安全。

1
2
3
4
5
6
7
8
9
10
var value: unknown = "hello";
value = 42;
value = true;

// var str: string = value; // 编译器会报错

if (typeof value === "string") {
var str: string = value;
console.log("字符串长度: " + str.length);
}

运行结果:字符串长度: 5

any 类型

any 完全绕过类型检查,可以被赋值为任何类型,也可以赋值给任何类型。应该尽量避免使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
var anything: any = "hello";
anything = 42;
anything = true;

var str: string = anything;
var num: number = anything;

console.log("字符串: " + str);
console.log("数字: " + num);

var obj: any = {};
obj.foo();
obj.bar = "value";

any vs unknown 对比

特性anyunknown
接受任意类型
直接赋值给其他类型否(需检查)
直接调用方法否(需检查)
类型安全否(无)是(有)

建议: 处理未知类型的数据时,优先使用 unknown 而不是 any,这样能强制进行类型检查。

never vs void 对比

特性nevervoid
含义永不返回没有返回值
用于函数抛出异常/无限循环普通无返回值函数
可赋值给其他类型是(子类型)只能赋值给 void/any

never 表示函数永远不会正常返回(要么抛异常,要么死循环);void 表示函数正常执行完毕,但没有返回值。

实际应用:穷举检查

never 类型常用于穷举检查(Exhaustive Check),确保所有分支都被处理。如果新增了一个分支却没有更新 switch,编译器就会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Shape = { kind: "circle", radius: number }
| { kind: "square", side: number };

function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default:
var _exhaustive: never = shape;
return _exhaustive;
}
}

var circle = { kind: "circle" as const, radius: 5 };
var square = { kind: "square" as const, side: 4 };

console.log("圆形面积: " + area(circle).toFixed(2));
console.log("正方形面积: " + area(square));

运行结果:

1
2
圆形面积: 78.54
正方形面积: 16

特殊类型注意事项

  • never 可以赋值给任何类型,但任何类型都不能赋值给 never
  • 在 JavaScript 中,voidundefined 本质上等价。
  • unknown 类型的值在使用前必须检查。
  • 尽量避免 any,优先使用 unknown

编写代码时,优先使用更严格的类型。处理未知数据使用 unknown,函数无返回值使用 void,需要穷举检查时使用 never

null 和 undefined

在 JavaScript 中,nullundefined 是常见的错误来源,很多运行时错误都与之相关。TypeScript 的 strictNullChecks 选项要求开发者显式处理可能为空的值,这虽然增加了编码工作量,但能大幅减少空值导致的运行时错误,提高代码可靠性。

null 表示"空值",即这里没有值;undefined 表示"未定义",即这里还没有被赋值。

null 和 undefined 基础

nullundefined 在 TypeScript 中是独立的类型。不启用 strictNullChecks 时,它们可以相互赋值;启用后需要显式声明。

1
2
3
4
5
6
7
8
// 声明 null 类型的变量,只能赋值为 null
var empty: null = null;

// 声明 undefined 类型的变量,只能赋值为 undefined
var notDefined: undefined = undefined;

console.log("null: " + empty);
console.log("undefined: " + notDefined);

运行结果:

1
2
null: null
undefined: undefined

联合类型处理 null

启用 strictNullChecks 后,需要使用联合类型显式声明可能为 null 的值,并通过类型守卫收窄类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 联合类型:可以是字符串或 null
var name: string | null = "Alice";
name = null; // 正确:可以赋值为 null

// 访问可能为 null 的值需要先检查
function getLength(str: string | null): number {
if (str === null) {
return 0; // null 时返回 0
}
// TypeScript 会推断 str 不是 null
return str.length;
}

console.log("长度: " + getLength("hello"));
console.log("长度: " + getLength(null));

运行结果:

1
2
长度: 5
长度: 0

最佳实践: 使用类型守卫(if 检查)来收窄类型,让 TypeScript 知道具体是什么类型。

可选参数和属性

可选参数和可选属性自动包含 undefined,不需要显式声明 null。可选参数 name?: string 等同于 string | undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 可选参数:使用 ? 标记
function greet(name?: string): string {
if (name === undefined) {
return "Hello, stranger!";
}
return "Hello, " + name;
}

console.log(greet("Alice"));
console.log(greet());

// 可选属性:使用 ? 标记
interface User {
name: string;
age?: number; // 可选属性
}

var user: User = { name: "Bob" };
console.log("用户: " + JSON.stringify(user));

运行结果:

1
2
3
Hello, Alice!
Hello, stranger!
用户: {"name":"Bob"}

非空断言运算符

使用 ! 告诉编译器值不为 nullundefined。这应该谨慎使用,因为它会绕过类型检查,如果值实际为 null,运行时仍会报错。

1
2
3
4
5
6
7
function getLength(str: string | null): number {
// 非空断言:告诉编译器 str 不为 null
// 这是一个危险的写法,如果 str 真的为 null 会报错
return str!.length;
}

console.log("长度: " + getLength("hello"));

空值合并运算符

使用 ?? 提供默认值,只有当值为 nullundefined 时才使用默认值。优先使用 ?? 而不是 ||,因为 ?? 只会处理 nullundefined,不会错误地将 0 或空字符串视为 falsy。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始值为 null
var name: string | null = null;

// ?? 运算符:左侧为 null/undefined 时使用右侧值
var displayName = name ?? "Guest";
console.log("显示名称: " + displayName);

// 对比 || 运算符(会把 0 和空字符串视为 falsy)
var num: number | null = 0;
var result1 = num ?? 100; // 0(正确:0 不是 null/undefined)
var result2 = num || 100; // 100(错误:0 被视为 falsy)

console.log("?? 结果: " + result1);
console.log("|| 结果: " + result2);

运行结果:

1
2
3
显示名称: Guest
?? 结果: 0
|| 结果: 100

可选链

使用 ?. 安全访问可能不存在的嵌套属性,避免多层嵌套时的空值检查。可选链是处理嵌套可选属性的最佳方式,配合 ?? 可以提供默认值的兜底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义人员类型,地址是可选的
interface Person {
name: string;
address?: {
city: string;
};
}

// 创建人员对象,没有地址
var person: Person = { name: "Alice" };

// 可选链:安全访问可能不存在的属性
var city = person.address?.city;

// 结合空值合并运算符
console.log("城市: " + city);
console.log("城市: " + (person.address?.city ?? "未知"));

运行结果:

1
2
城市: undefined
城市: 未知

null/undefined 处理总结

  • 严格模式: 建议启用 strictNullChecks 以获得更好的类型安全。
  • 联合类型: 使用 string | null 声明可能为空的值。
  • 可选参数/属性: 自动包含 undefined
  • 非空断言: 使用 !(谨慎使用,尽量避免)。
  • 空值合并: 使用 ?? 提供默认值。
  • 可选链: 使用 ?. 安全访问。

使用类型守卫、可选链和空值合并运算符来处理 nullundefined,让代码更安全、更易读。

Symbol

Symbol 是 ES6 引入的原始数据类型,表示唯一的标识符。在 JavaScript 中,对象的属性名都是字符串,有时可能会发生冲突。Symbol 提供了创建唯一标识符的方式,每次调用 Symbol() 都会创建一个新的、唯一的值,即使描述相同也不相等。这在需要创建私有属性、定义唯一常量、实现迭代器等场景非常有用。

创建 Symbol

使用 Symbol() 函数创建唯一的 Symbol 值,可以传入一个可选的描述字符串。

1
2
3
4
5
6
7
// 创建 Symbol,传入描述字符串(可选)
var sym1 = Symbol("description");
var sym2 = Symbol("description");

// 每次创建的 Symbol 都是唯一的,即使描述相同
console.log("sym1 === sym2: " + (sym1 === sym2));
console.log("sym1: " + sym1.toString());

运行结果:

1
2
sym1 === sym2: false
sym1: Symbol(description)

这是 Symbol 最重要的特性:每次调用 Symbol() 都会创建一个新值,与任何其他值都不相等。

Symbol 作为对象属性

Symbol 可以用作对象的属性键,创建唯一的属性名。Symbol 属性不会出现在 JSON 序列化中,也不会被 for...in 遍历到,可以用来创建"私有"属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建 Symbol 作为属性键
var sym = Symbol("key");

// 使用 Symbol 作为对象的属性键
var obj = {
name: "Alice", // 普通字符串属性
[sym]: "secret value" // Symbol 属性,计算属性名
};

// 访问普通属性
console.log("普通属性: " + obj.name);
// 访问 Symbol 属性
console.log("Symbol 属性: " + obj[sym]);
// Symbol 属性不会出现在 JSON 中
console.log("对象: " + JSON.stringify(obj));

运行结果:

1
2
3
普通属性: Alice
Symbol 属性: secret value
对象: {"name":"Alice"}

全局 Symbol 注册表

使用 Symbol.for() 访问全局注册表中的 Symbol,相同 key 会返回相同的 Symbol。这与 Symbol() 不同:Symbol() 每次创建新的值,Symbol.for() 在全局注册表中查找或创建。

1
2
3
4
5
6
7
8
9
// 使用 Symbol.for 方法创建/获取全局 Symbol
var globalSym1 = Symbol.for("global");
var globalSym2 = Symbol.for("global");

// 相同 key 的 Symbol 是相等的
console.log("全局 Symbol 相等: " + (globalSym1 === globalSym2));

// 获取 Symbol 的 key
console.log("Symbol key: " + Symbol.keyFor(globalSym1));

运行结果:

1
2
全局 Symbol 相等: true
Symbol key: global

内置 Symbol

JavaScript 定义了一些内置的 Symbol 值,用于自定义语言行为,是 JavaScript 高级特性的基础。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Symbol.iterator 用于定义对象的默认迭代器
var arr = [1, 2, 3];
// 获取数组的迭代器
var iterator = arr[Symbol.iterator]();

// 使用迭代器遍历
console.log("第一个元素: " + iterator.next().value);
console.log("第二个元素: " + iterator.next().value);

// Symbol.toStringTag 自定义对象的 toString() 返回值
var obj = {
[Symbol.toStringTag]: "MyObject"
};
console.log("对象类型: " + obj.toString());

运行结果:

1
2
3
第一个元素: 1
第二个元素: 2
对象类型: [object MyObject]

常见的内置 Symbol 包括:

  • Symbol.iterator:定义对象的默认迭代器。
  • Symbol.toStringTag:自定义对象的 toString() 返回值。
  • Symbol.hasInstance:自定义 instanceof 的行为。

Symbol 类型注解

在 TypeScript 中使用 symbol 类型(小写)进行类型注解。

1
2
3
4
5
6
7
8
9
// Symbol 类型注解
var sym: symbol = Symbol("key");

// 对象的键类型为 symbol,值为 string
var obj: { [key: symbol]: string } = {};

// 使用 Symbol 作为键
obj[sym] = "value";
console.log("Symbol 属性值: " + obj[sym]);

Symbol 注意事项

  • 唯一性: 每次 Symbol() 调用的值都不同。
  • 不可枚举: Symbol 属性不会出现在 for...in 循环中。
  • JSON 忽略: Symbol 属性不会被 JSON 序列化。
  • 全局注册: Symbol.for() 在全局注册表中共享。

在需要创建唯一标识符、避免属性名冲突、或需要"私有"属性时,使用 Symbol。

Number

TypeScript 与 JavaScript 一样支持 Number 对象。Number 对象用于包装数值类型,是引用类型,与基本的 number 类型不同。虽然 Number 对象提供了额外的属性和方法,但在 TypeScript 中更推荐直接使用基本的 number 类型。

语法

1
var num = new Number(value);

这会创建一个引用类型的对象,而不是原始的 number 类型。如果参数无法转换为数字,则返回 NaN

Number 对象与 number 类型的区别

  • 基本类型 number:原始数据类型,用于存储数值。
  • Number 对象:引用类型,是一个包装对象,用于包装基本数值。
1
2
3
4
5
let numLiteral: number = 42;
let numObject: Number = new Number(42);

console.log(typeof numLiteral); // 输出:"number"
console.log(typeof numObject); // 输出:"object"

Number 对象属性

序号属性 & 描述
1MAX_VALUE — 可表示的最大的数,接近 1.79E+308。大于此值为 Infinity
2MIN_VALUE — 最接近 0 的正数,约 5e-324。小于此值转换为 0。
3NaN — 非数字值(Not-A-Number)。
4NEGATIVE_INFINITY — 负无穷大,溢出时返回。小于 MIN_VALUE。
5POSITIVE_INFINITY — 正无穷大,溢出时返回。大于 MAX_VALUE。
6prototype — 静态属性,允许向对象添加属性和方法。
7constructor — 返回创建此对象的 Number 函数引用。
1
2
3
4
5
console.log("TypeScript Number 属性: ");
console.log("最大值为: " + Number.MAX_VALUE);
console.log("最小值为: " + Number.MIN_VALUE);
console.log("负无穷大: " + Number.NEGATIVE_INFINITY);
console.log("正无穷大: " + Number.POSITIVE_INFINITY);

输出结果:

1
2
3
4
5
TypeScript Number 属性:
最大值为: 1.7976931348623157e+308
最小值为: 5e-324
负无穷大: -Infinity
正无穷大: Infinity

NaN 实例

1
2
3
4
5
6
7
var month = 0;
if (month <= 0 || month > 12) {
month = Number.NaN;
console.log("月份是:" + month);
} else {
console.log("输入月份数值正确。");
}

输出:月份是:NaN

prototype 实例

1
2
3
4
5
6
7
8
9
function employee(id: number, name: string) {
this.id = id;
this.name = name;
}
var emp = new employee(123, "admin");
employee.prototype.email = "admin@runoob.com";
console.log("员工号: " + emp.id);
console.log("员工姓名: " + emp.name);
console.log("员工邮箱: " + emp.email);

输出结果:

1
2
3
员工号: 123
员工姓名: admin
员工邮箱: admin@runoob.com

Number 对象方法

序号方法 & 描述实例
1toExponential() — 把对象的值转换为指数计数法。var num1 = 1225.30; var val = num1.toExponential(); console.log(val); // 1.2253e+3
2toFixed() — 把数字转换为字符串,并对小数点指定位数。var num3 = 177.234; console.log(num3.toFixed()); // 177; console.log(num3.toFixed(2)); // 177.23; console.log(num3.toFixed(6)); // 177.234000
3toLocaleString() — 把数字转换为字符串,使用本地数字格式顺序。var num = new Number(177.1234); console.log(num.toLocaleString()); // 177.1234
4toPrecision() — 把数字格式化为指定的长度。var num = new Number(7.123456); console.log(num.toPrecision()); // 7.123456; console.log(num.toPrecision(1)); // 7; console.log(num.toPrecision(2)); // 7.1
5toString() — 把数字转换为字符串,使用指定的基数(2~36),省略则用基数 10。var num = new Number(10); console.log(num.toString()); // 10; console.log(num.toString(2)); // 1010; console.log(num.toString(8)); // 12
6valueOf() — 返回一个 Number 对象的原始数字值。var num = new Number(10); console.log(num.valueOf()); // 10

注意: toFixed()toPrecision() 返回的是 string 类型,而不是 number 类型。

1
2
3
var a = 177.234;
var b = a.toFixed();
console.log(b, typeof b); // 177 string

Number 使用建议

在 TypeScript 中更推荐直接使用基本的 number 类型,而不是 Number 对象,原因有三:

  • 性能:基本类型更轻量,性能更好。
  • 类型一致性:TypeScript 的类型系统更倾向于基本类型,使用 Number 对象可能导致意外的类型不匹配。
  • 最佳实践:基本类型的 number 更符合 TypeScript 的最佳实践,避免了对象包装带来的不必要复杂性。

如果确实需要 Number 对象的方法,可以使用 valueOf() 转换回原始值:

1
2
3
4
5
let numLiteral: number = 123.456;
let numObject: Number = new Number(123.456);

console.log(numLiteral.toFixed(2)); // 输出:"123.46"
console.log(numObject.valueOf()); // 输出:123.456

String

在 TypeScript 中,string 类型表示文本数据。它继承并扩展了 JavaScript 的字符串能力,同时增加了静态类型检查。

创建字符串

有两种创建字符串的形式:

1
2
var txt = new String("string");  // String 对象(不推荐)
var txt = "string"; // 字符串字面量(推荐)

方式一:字符串字面量(推荐)

这是最常见且性能最好的方式,创建的是原始的 string 类型,是 TypeScript 类型系统中默认的字符串类型。

1
2
const txt1: string = "Hello TypeScript";
const txt2 = "Hello JavaScript";

方式二:String 对象(不推荐)

使用 new String() 创建的是包装对象类型(String),它是一个对象而非原始值,会引入类型混淆和性能开销。

1
const txtObj: String = new String("Hello Object");

string 与 String 的区别

字符串字面量和 String 对象在类型上不同:字面量是原始的 string 数据类型,而 String 对象是引用类型。

1
2
3
4
5
let strLiteral: string = "Hello";
let strObject: String = new String("Hello");

console.log(typeof strLiteral); // "string"
console.log(typeof strObject); // "object"
特性字符串字面量 (string)String 对象 (String)
类型本质原始值引用类型(对象)
性能高效,无额外开销低效,创建对象实例
类型检查符合 TS 基本类型类型不匹配(string 变量不能接受 String 对象)
比较直接值比较引用比较(需用 .valueOf() 获取原始值)

在 TypeScript 中,string 字面量类型和 String 对象类型并不完全兼容:

1
2
3
4
5
6
let strLiteral: string = "Test";
let strObject: String = new String("Test");

console.log(strLiteral === strObject); // false,类型不同
console.log(strLiteral == strObject); // true,内容相同
console.log(strLiteral === strObject.valueOf()); // true,转换后相同

String 对象属性

序号属性 & 描述
1constructor — 对创建对象的函数的引用。var str = new String("This is string"); console.log("str.constructor is:" + str.constructor); 输出 str.constructor is:function String() { [native code] }
2length — 返回字符串的长度。var uname = new String("Hello World"); console.log("Length " + uname.length); 输出 11
3prototype — 允许向对象添加属性和方法。

String 方法

序号方法 & 描述
1charAt() — 返回指定位置的字符。var str = new String("RUNOOB"); console.log(str.charAt(0)); // R
2charCodeAt() — 返回指定位置字符的 Unicode 编码。console.log(str.charCodeAt(0)); // 82
3concat() — 连接两个或多个字符串,返回新字符串。var str3 = str1.concat(str2);
4indexOf() — 返回指定值首次出现的位置。str1.indexOf("OO"); // 3
5lastIndexOf() — 从后向前搜索,返回指定值最后出现的位置。
6localeCompare() — 用本地特定顺序比较两个字符串。
7match() — 找到一个或多个正则表达式的匹配。str.match(/ain/g);
8replace() — 替换与正则表达式匹配的子串。str.replace(re, "$2, $1");
9search() — 检索与正则表达式匹配的值。
10slice() — 提取字符串的片断,返回新字符串。
11split() — 把字符串分割为子字符串数组。str.split(" ", 3);
12substr() — 从起始索引号提取指定数目的字符。
13substring() — 提取两个指定索引之间的字符。str.substring(1, 2); // U
14toLocaleLowerCase() — 根据主机 locale 转为小写。
15toLocaleUpperCase() — 根据主机 locale 转为大写。
16toLowerCase() — 转为小写。
17toString() — 返回字符串。
18toUpperCase() — 转为大写。
19valueOf() — 返回 String 对象的原始值。

方法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var str = new String("RUNOOB");
console.log("str.charAt(0) 为:" + str.charAt(0)); // R
console.log("str.charCodeAt(0) 为:" + str.charCodeAt(0)); // 82

var str1 = new String("RUNOOB");
var str2 = new String("GOOGLE");
var str3 = str1.concat(str2);
console.log("str1 + str2 : " + str3); // RUNOOBGOOGLE

var index = str1.indexOf("OO");
console.log("查找的字符串位置 :" + index); // 3

var strSub = "RUNOOB GOOGLE TAOBAO FACEBOOK";
console.log("(1,2): " + strSub.substring(1, 2)); // U
console.log("(0,10): " + strSub.substring(0, 10)); // RUNOOB GOO
console.log("(5): " + strSub.substring(5)); // B GOOGLE TAOBAO FACEBOOK

var strLower = "Runoob Google";
console.log(strLower.toLowerCase()); // runoob google
console.log(strLower.toUpperCase()); // RUNOOB GOOGLE

String 使用建议

在 TypeScript 中,通常没有必要使用 String 对象。直接使用 string 字面量更高效,也更符合最佳实践:

  • 性能String 对象是引用类型,占用更多内存,每次创建实例开销更大。
  • 类型安全:TypeScript 鼓励使用 string 字面量类型,代码更简洁、更一致。

如果确实需要 String 对象的方法,可以先调用 .valueOf() 将对象转换为原始字符串:

1
2
3
4
5
let strLiteral: string = "Use string literals whenever possible!";
let strObject: String = new String("Avoid using String objects.");

console.log(strLiteral); // "Use string literals whenever possible!"
console.log(strObject.valueOf()); // "Avoid using String objects."

类型推断

类型推断是 TypeScript 最强大和便捷的特性之一。当没有明确指定类型时,TypeScript 编译器会分析代码上下文,自动确定变量类型——包括变量的初始值、函数的返回值等。类型推断让开发者可以在大多数情况下省略显式的类型声明,写出既简洁又类型安全的代码。

基础类型推断

当使用 varletconst 声明变量并赋予初始值时,TypeScript 会根据初始值推断变量类型。

1
2
3
4
5
6
7
var num = 10;        // 推断为 number
var str = "hello"; // 推断为 string
var isActive = true; // 推断为 boolean

console.log("num 类型: " + typeof num);
console.log("str 类型: " + typeof str);
console.log("isActive 类型: " + typeof isActive);

一旦推断出类型,后续赋值必须匹配该类型,否则编译报错:

1
2
3
4
var num = 2;    // 类型推断为 number
console.log("num 变量的值为 " + num);
num = "12"; // 编译错误
console.log(num);

错误信息:error TS2322: Type '"12"' is not assignable to type 'number'.

函数返回类型推断

TypeScript 会根据函数中的 return 语句推断返回类型。

1
2
3
4
5
6
7
function add(a: number, b: number) {
return a + b; // 返回类型推断为 number
}

function greet(name: string) {
return "Hello, " + name; // 返回类型推断为 string
}

提示: 在实际项目中,建议显式声明函数的返回类型,提高代码可读性。

上下文类型推断

当变量出现在特定的上下文位置时,TypeScript 会根据使用位置反推其类型。

1
2
3
4
5
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function(n) {
return n * 2; // n 自动推断为 number,来自数组上下文
});
console.log("翻倍数组: " + doubled);

输出:翻倍数组: 2,4,6,8,10

最佳通用类型推断

当数组的初始值包含多种类型时,TypeScript 会推断出联合类型。

1
2
var mixed = [1, "two", 3, "four"];
// 推断为 (number | string)[]

类型推断的限制

如果声明变量时没有初始值,TypeScript 会将其推断为 any,这会失去类型安全。

1
2
3
4
5
var unknown;  // 推断为 any —— 没有初始值
unknown = "hello";
unknown = 123;

var fixedNumber: number = 42; // 推荐显式声明类型

警告: 避免使用未初始化的变量——应提供初始值或显式声明类型。

泛型函数推断

TypeScript 会根据函数调用时的参数自动推断泛型类型参数。

1
2
3
4
5
6
function identity<T>(arg: T): T {
return arg;
}

var str = identity("hello"); // T 推断为 string
var num = identity(42); // T 推断为 number

类型推断注意事项

  • 初始值的重要性: 始终为变量提供初始值以获得正确的类型推断。
  • 显式声明: 当类型复杂或不明确时,显式声明类型。
  • any 类型的风险: 未初始化的变量会成为 any,失去类型安全。
  • strict 模式: 启用严格模式以避免 any 的滥用。

善用类型推断可以减少冗余代码,但在关键位置仍应显式声明类型,写出既简洁又类型安全的代码。

类型断言

类型断言(Type Assertion)是一种告诉编译器"我比你更清楚这个值的类型"的机制。它赋予开发者手动覆盖 TypeScript 类型推断结果的能力,类似其他语言中的类型转换,但仅在编译阶段生效,不产生任何运行时代码。

两种语法形式

类型断言有两种写法:

1
2
3
4
5
6
7
8
9
10
// ① 尖括号语法(不推荐,在 .tsx 文件中无法使用)
const str1: any = "hello";
const len1: number = (<string>str1).length;

// ② as 语法(推荐)
const str2: any = "world";
const len2: number = (str2 as string).length;

console.log(len1); // 5
console.log(len2); // 5

运行结果:

1
2
5
5

在 React 的 .tsx 文件中,尖括号写法会与 JSX 标签冲突,必须使用 as 语法。推荐统一使用 as 语法。

TypeScript 判断断言是否有效的规则是:当类型 S 是类型 T 的子类型,或 T 是 S 的子类型时,S 能被成功断言成 T。这提供了额外的安全性——毫无根据的断言是危险的,如需要可以使用 any 作为中转。

类型断言不被称为类型转换,因为转换意味着运行时的支持,而类型断言纯粹是一个编译时语法,只用于指导编译器如何分析代码。

处理 any / unknown 类型

从外部接口、JSON 解析等场景获取的数据通常为 any,通过断言可以恢复类型提示。

1
2
3
4
5
6
7
const response: any = { name: "Alice", age: 25 };

// 断言为具体类型,获得类型提示
const user = response as { name: string; age: number };

console.log(user.name); // Alice
console.log(user.age); // 25

收窄联合类型

当已知联合类型中的具体分支,但编译器无法自动判断时,可使用断言明确类型。应优先使用类型守卫(typeof / instanceof / 判别属性)收窄类型,只在守卫无法覆盖的场景才使用断言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };

function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * (shape as { kind: "circle"; radius: number }).radius ** 2;
}
const rect = shape as { kind: "rect"; width: number; height: number };
return rect.width * rect.height;
}

console.log(getArea({ kind: "circle", radius: 5 }).toFixed(2)); // 78.54
console.log(getArea({ kind: "rect", width: 4, height: 6 })); // 24

操作 DOM 元素

document.querySelector 返回的是 Element | null,断言为具体元素类型后才能访问其专属属性。

1
2
3
4
5
6
7
8
9
10
11
// querySelector 返回 Element | null
const input = document.querySelector("#username") as HTMLInputElement;

// 断言后可访问 HTMLInputElement 的专属属性
// console.log(input.value);

// 更安全的写法:先判空再断言
const btn = document.querySelector("#submit");
if (btn) {
(btn as HTMLButtonElement).disabled = true;
}

非空断言 !

在值后加 !,告诉编译器该值不为 nullundefined,适用于你能确保非空但编译器无法推断的场景。非空断言会绕过编译器的空值检查,若实际值为 null / undefined,运行时仍会抛出错误。在条件允许时优先使用可选链 ?. 代替。

1
2
3
4
5
6
7
8
9
10
function printLength(str?: string) {
// 用 ! 断言 str 一定有值
console.log(str!.length);
}

// 运行正常
printLength("hello"); // 5

// 运行时报错:Cannot read properties of undefined
// printLength();

常量断言 as const

as const 将字面量推断为最窄的只读字面量类型,常用于定义枚举值、配置常量和元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 普通声明:推断为 string[],元素可修改
const colors1 = ["red", "green", "blue"];
colors1.push("yellow"); // 允许

// as const:推断为 readonly ["red", "green", "blue"],元素和长度均不可变
const colors2 = ["red", "green", "blue"] as const;
// colors2.push("yellow"); // 错误:只读数组不能 push

// 对象同理:所有属性变为 readonly 字面量类型
const config = {
host: "localhost",
port: 3000
} as const;
// config.port = 8080; // 错误:只读属性不能修改

console.log(colors2); // ['red', 'green', 'blue']
console.log(config.host); // localhost
console.log(config.port); // 3000

断言不是类型转换

类型断言只影响编译器的类型检查,编译后不生成任何转换代码。将 "42" 断言为 number,运行时它仍然是字符串。真正的类型转换需要使用 Number()String() 等转换函数。

1
2
3
4
5
6
7
8
9
10
11
const strNum: any = "42";

// 断言为 number,但运行时仍是 string
const wrongNum = strNum as number;
console.log(typeof wrongNum); // string(不是 number!)
console.log(wrongNum + 1); // 421(字符串拼接,不是数字相加)

// 真正的类型转换需要使用转换函数
const realNum = Number(strNum);
console.log(typeof realNum); // number
console.log(realNum + 1); // 43

双重断言

当两个类型之间没有重叠关系时,TypeScript 会拒绝直接断言。此时可借助 unknown 作为中转,但这是危险操作,应尽量避免。双重断言会完全绕过 TypeScript 的类型系统,极易引发运行时错误,仅在处理第三方库类型不准确等特殊情况下使用,并在代码中注明原因。

1
2
3
4
5
6
7
8
9
10
const num = 42;

// 直接断言:number 与 string 无重叠,编译器报错
// const str = num as string;

// 双重断言:先断言为 unknown,再断言为目标类型
const str = num as unknown as string;

console.log(str); // 42
console.log(typeof str); // number(运行时类型未变,断言只在编译期生效)

断言方式对比

断言方式语法典型场景风险
as 断言value as Typeany 转具体类型、DOM 操作低(有重叠检查)
非空断言value!确认非 null/undefined中(跳过空值检查)
常量断言value as const字面量常量、枚举值
双重断言value as unknown as T类型无重叠时强制转换高(完全绕过检查)

类型断言总结

  • 语法选择: 统一使用 as 语法,避免在 JSX 中使用尖括号写法。
  • 断言 vs 转换: 断言仅影响编译期类型,不产生运行时代码,真正的值转换需使用 Number()String() 等函数。
  • 非空断言: 使用 ! 前确保值真的非空,否则运行时会报错,条件允许时优先用 ?.
  • 常量断言: as const 是安全且实用的断言,推荐用于配置对象和常量数组。
  • 双重断言: 是最后手段,使用时必须注释说明原因。

总结

TypeScript 的类型系统是其相对于 JavaScript 的核心优势。本章覆盖了以下要点:

  • 基础类型: stringnumberbooleanarraytupleenumanyvoidnullundefinedneverobject、联合类型、unknown、字面量类型构成了完整的类型体系。
  • 变量声明: var/let/const 三种声明方式各有适用场景,配合类型注解和解构语法,可以灵活而安全地声明变量。
  • 特殊类型: anyunknownvoidnever 各有用途,类型安全等级从高到低为 never > void > unknown > any,应优先使用更严格的类型。
  • null/undefined: 启用 strictNullChecks 后,使用联合类型、可选参数、可选链 ?. 和空值合并 ?? 安全处理空值。
  • Symbol: 唯一标识符,用于避免属性名冲突和创建"私有"属性。
  • Number/String: 推荐使用原始类型 number/string 而非包装对象 Number/String
  • 类型推断: 编译器自动推断类型,减少冗余声明,但在关键位置应显式声明。
  • 类型断言: as 语法手动指定类型,配合 as const! 等机制灵活控制类型检查。

掌握这些基础,就为后续学习函数、接口、泛型、类等高级特性打下了坚实基础。