第2章 类型基础:原始类型与类型注解

第 2 章 类型基础:原始类型与类型注解

2.1 类型概述

在正式进入TypeScript的类型世界之前,我们需要先了解一下"类型"这个概念。

想象你是一个仓库管理员。你的仓库里有很多货物,你需要知道每个货物是什么类型的——哪些是水果、哪些是电器、哪些是易碎品。只有知道货物的类型,你才能正确地处理它们。

编程世界里的"类型"也是一样的道理。类型告诉你这个数据是什么、它能做什么、不能做什么


2.1.1 JavaScript 原始类型

JavaScript有7种原始类型(Primitive Types),也可以叫"基本类型"——它们是语言中最底层的数据类型。

1
2
3
4
5
6
7
8
// 7种原始类型一览
const str = "你好";          // string - 字符串
const num = 42;              // number - 数字
const big = 9007199254740991n; // bigint - 大整数
const bool = true;          // boolean - 布尔值
const nothing = undefined;   // undefined - 未定义
const empty = null;         // null - 空值
const sym = Symbol("id");   // symbol - 唯一标识符

这7种原始类型可以分为两组:

有实际值的:string、number、bigint、boolean、symbol 空/无值的:undefined、null

在TypeScript中,这7种原始类型都有对应的类型注解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let name: string = "孙悟空";
let age: number = 500;
let bigNumber: bigint = 9007199254740991n;
let isMonkey: boolean = true;
let notYetDefined: undefined = undefined;
let emptyValue: null = null;
let uniqueId: symbol = Symbol("id");

console.log(typeof name);         // string
console.log(typeof age);          // number
console.log(typeof bigNumber);     // bigint
console.log(typeof isMonkey);      // boolean
console.log(typeof notYetDefined); // undefined
console.log(typeof emptyValue);    // object (这是JS的bug,null显示为object)
console.log(typeof uniqueId);      // symbol

💡 小知识:为什么typeof null返回'object'而不是'null'?这是JavaScript的第一个bug,源于早期实现的一个历史遗留问题。具体原因在第一章已经讲过,这里就不再赘述。记住这个"坑"就好,以后面试可能会考。


2.1.2 TypeScript 新增类型

除了JavaScript的7种原始类型,TypeScript还额外提供了几种类型,它们只存在于TypeScript的编译时,不存在于JavaScript运行时。

2.1.2.1 any、unknown、void、never(无对应 JS 运行时概念)

这四个类型是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
// any:任意类型,相当于"关闭类型检查"
let anything: any = "可以是任何东西";
anything = 42;          // 完全OK
anything = true;        // 也OK
anything = null;        // 都可以

// unknown:未知类型,比any更安全
let unknown: unknown = "这是一个未知类型的值";
// unknown.toUpperCase(); // 错误!unknown类型不能直接操作
// 必须先检查类型才能使用
if (typeof unknown === "string") {
    console.log(unknown.toUpperCase()); // OK!
}

// void:表示函数没有返回值(或返回undefined)
function sayHello(): void {
    console.log("你好!");
    // 没有return语句,等价于 return undefined
}

// never:表示永远不会返回(死循环或抛异常)
function throwError(): never {
    throw new Error("出错了!");
}

function infiniteLoop(): never {
    while (true) {
        // 永远不停止
    }
}

2.1.2.2 enum:TS 新增的语法,编译为 JS 对象(IIFE),JS 本身无 enum 关键字

enum(枚举)是TypeScript新增的语法,在JavaScript中没有对应的概念:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 数字枚举
enum Direction {
    Up,    // 0
    Down,  // 1
    Left,  // 2
    Right  // 3
}

console.log(Direction.Up);    // 0
console.log(Direction[0]);   // "Up" —— 反向映射

// 字符串枚举
enum Status {
    Success = "SUCCESS",
    Error = "ERROR",
    Loading = "LOADING"
}

console.log(Status.Success); // "SUCCESS"

编译成JavaScript后:

1
2
3
4
5
6
7
8
// 编译后的JavaScript
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

可以看到,TypeScript的枚举被编译成了一个立即执行函数表达式(IIFE),里面有一个对象。

2.1.2.3 元组:TS 新增的纯类型构造(固定长度 + 固定位置类型),JS 无对应概念,TS 用 Array 实现

元组(Tuple)是TypeScript特有的类型,用于表达固定长度、每个位置有固定类型的数组:

1
2
3
4
5
6
7
8
// 元组类型:固定长度 + 固定位置类型
let person: [string, number] = ["孙悟空", 500];

console.log(person[0]); // "孙悟空" —— 第一位是string
console.log(person[1]); // 500 —— 第二位是number

// person[2] = true; // 错误!元组长度是固定的
// person = [1, 2, 3]; // 错误!长度必须是2

元组在JavaScript中没有对应概念,TypeScript用数组来实现它,但加上了类型约束。


2.1.3 类型注解与类型推断

2.1.3.1 显式注解:const a: number = 5

显式类型注解就是你明确告诉TypeScript这个变量是什么类型

1
2
3
4
5
// 显式注解:变量名后面加冒号和类型
const name: string = "猪八戒";
const age: number = 3000;
const height: number = 1.85;
const isMarried: boolean = false;

2.1.3.2 隐式推断:const a = 5 → 推导为 number

TypeScript会根据变量的初始值自动推导出类型:

1
2
3
4
5
6
7
// 隐式推断:TypeScript会根据=右边的值推断变量类型
const name = "沙和尚";     // TypeScript推断:string
const age = 800;           // TypeScript推断:number
const isStrong = true;     // TypeScript推断:boolean

// 推断后,类型就固定了,不能再赋值为其他类型
name = 123;  // 错误!Type 'number' is not assignable to type 'string'

2.1.3.3 推断失败时 noImplicitAny 报错

如果TypeScript无法推断出类型,而且你没有显式注解,就会报noImplicitAny错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 没有初始值,也没有注解,TS无法推断类型
function process(value) {
    console.log(value.toUpperCase()); // 错误!value是any类型
}

// 解决方式1:加类型注解
function process(value: string) {
    console.log(value.toUpperCase()); // OK
}

// 解决方式2:加初始值
function process() {
    const value = "hello";
    console.log(value.toUpperCase()); // OK
}

2.2 string、number、boolean 类型

这三个类型是TypeScript(也是JavaScript)中最常用的基础类型。它们看起来简单,但里面有一些细节值得好好聊聊。


2.2.1 string:字符串字面量类型 vs string、模板字符串类型推导、常用方法返回类型

string类型代表字符串,这没什么好多说的。但有几个点需要注意:

字符串字面量类型 vs string

在TypeScript中,string代表所有字符串。而字符串字面量类型只代表某个具体的字符串:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// string:所有字符串的总称
let greeting: string = "你好";
greeting = "Hello";      // OK,任何字符串都行
greeting = "Bonjour";    // OK

// 字符串字面量类型:只代表某个具体字符串
let specificGreeting: "你好" = "你好";
specificGreeting = "Hello"; // 错误!只能赋值"你好"

type Direction = "Up" | "Down" | "Left" | "Right";
let move: Direction = "Up";
move = "Down";      // OK
move = "Diagonal";  // 错误!"Diagonal"不在允许的范围内

字符串字面量类型常用于限制只能取特定值的场景,比如方向、状态、错误码等。

模板字符串类型推导

TypeScript可以推导模板字符串的类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 模板字符串的类型推导
let name = "孙悟空";
let age = 500;
let introduction = `我是${name},今年${age}岁`;
console.log(introduction); // 我是孙悟空,今年500岁
// 注释:introduction的类型是string

// 复杂的模板字符串
type Environment = "development" | "production" | "test";
const url = `https://api.example.com/${"development"}/users` as const;
// 注释:url的类型是 "https://api.example.com/development/users"(字面量类型)
console.log(url); // https://api.example.com/development/users

常用方法返回类型

字符串的很多方法会返回新的字符串,TypeScript能正确推导:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const original = "Hello World";
const upper = original.toUpperCase();
console.log(upper); // HELLO WORLD
// 注释:upper的类型是string

const lower = original.toLowerCase();
console.log(lower); // hello world
// 注释:lower的类型是string

const replaced = original.replace("World", "TypeScript");
console.log(replaced); // Hello TypeScript
// 注释:replaced的类型是string

const sliced = original.slice(0, 5);
console.log(sliced); // Hello
// 注释:sliced的类型是string

const trimmed = "  hello  ".trim();
console.log(trimmed); // hello
// 注释:trimmed的类型是string

2.2.2 number:整数字面量 vs 浮点数、NaN 的类型问题、数值边界常量

number类型代表JavaScript中的所有数字——包括整数和浮点数。

整数字面量 vs 浮点数

在TypeScript中,整数和浮点数都使用同一个number类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let integer: number = 42;
let floating: number = 3.14159;
let negative: number = -273.15;

console.log(integer);   // 42
console.log(floating);  // 3.14159
console.log(negative); // -273.15

// 科学计数法
let scientific: number = 1.5e10;
console.log(scientific); // 15000000000

NaN 的类型问题

还记得第一章说的NaN吗?NaN的类型是number,这很反直觉,但TS忠实地反映了JS的这个特点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
let nan: number = NaN;
console.log(nan); // NaN
console.log(typeof nan); // number

// 产生NaN的操作
let result: number = parseInt("hello");
console.log(result); // NaN
console.log(typeof result); // number

// NaN的类型检查
function processNumber(n: number) {
    if (Number.isNaN(n)) {
        console.log("这是个NaN!");
    } else {
        console.log("这是正常的数字:" + n);
    }
}

processNumber(NaN);       // 这是个NaN!
processNumber(42);        // 这是正常的数字:42

数值边界常量

JavaScript为number类型定义了一些边界常量:

 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
// 最大安全整数
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
// 注释:超过这个数的整数运算可能不精确

// 最大值
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
// 注释:JavaScript能表示的最大数字

// 最小值(最接近0的正数)
console.log(Number.MIN_VALUE); // 5e-324
// 注释:JavaScript能表示的最接近0的正数

// 正无穷
console.log(Number.POSITIVE_INFINITY); // Infinity
// 注释:比MAX_VALUE还大的数

// 负无穷
console.log(Number.NEGATIVE_INFINITY); // -Infinity
// 注释:比-MAX_VALUE还小的数

// 安全整数检查
function isSafeInteger(n: number): boolean {
    return Number.isSafeInteger(n);
}

console.log(isSafeInteger(9007199254740991)); // true
console.log(isSafeInteger(9007199254740992)); // false —— 超过安全范围!

2.2.3 boolean:字面量 true vs boolean、const 推断 vs let 推断

boolean类型只有两个值:truefalse

字面量 true vs boolean

跟字符串一样,boolean也有字面量类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// boolean类型:可以是true或false
let isActive: boolean = true;
isActive = false;  // OK

// boolean字面量类型:只能是true或只能是false
let isLoading: true = true;
isLoading = false; // 错误!只能是true

type Bit = 0 | 1;
let bit: Bit = 0;
bit = 1;  // OK
bit = 2;  // 错误!

const 推断 vs let 推断

const声明的变量,TypeScript会推断为字面量类型;用let声明的变量,TypeScript会推断为宽泛的类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// const声明:推断为字面量类型
const isOnline = true;
console.log(typeof isOnline); // boolean
// 注释:isOnline的类型是true(字面量类型),不是boolean

const status = "loading";
console.log(status); // loading
// 注释:status的类型是"loading"(字面量类型),不是string

// let声明:推断为宽泛类型
let isEnabled = true;
console.log(typeof isEnabled); // boolean
// 注释:isEnabled的类型是boolean,不是true

let name = "Tom";
console.log(name); // Tom
// 注释:name的类型是string,不是"Tom"

这个区别的实际意义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const constBoolean = true;
let letBoolean: boolean = true;

// constBoolean只能赋值true
// constBoolean = false; // 错误!

// letBoolean可以是true或false
letBoolean = false; // OK

// 函数参数的类型收窄
function checkStatus(status: "success" | "error" | "loading") {
    console.log("当前状态:" + status);
}

const currentStatus = "success";
checkStatus(currentStatus); // OK!currentStatus类型是"success"

let dynamicStatus = "success";
checkStatus(dynamicStatus); // 错误!因为dynamicStatus是string类型
checkStatus("success");    // OK!字面量直接传
checkStatus(dynamicStatus as "success"); // OK!强制类型转换

2.3 null 与 undefined

nullundefined是JavaScript(和TypeScript)里最让人困惑的两个"空"值。它们长得像,名字也像,但语义完全不同。


2.3.1 两种空值的语义区别

2.3.1.1 null:语义为空,通常代表「这里本应有值,但当前没有」

null表示**“这里应该有值,但是现在是空的”。它是一个主动**赋值的"空"。

典型使用场景:

  • API返回的数据中,某个字段本应有值但目前没有
  • 数据库中的空字段
  • 函数期望返回一个对象,但找不到时返回null
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// API返回的用户数据
interface User {
    name: string;
    email: string | null;  // email可能是null——用户没填
}

const user: User = {
    name: "孙悟空",
    email: null  // 用户没提供邮箱
};

console.log(user.name);  // 孙悟空
console.log(user.email); // null —— 本应有值,但没有

2.3.1.2 undefined:表示「未赋值」或「属性/参数不存在」

undefined表示**“这个值根本不存在或还没被赋值”。它是一个被动**的状态。

典型使用场景:

  • 变量声明但未初始化
  • 访问对象不存在的属性
  • 函数没有返回值
  • 函数调用时没有传参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 变量声明但未初始化
let pending: string;
console.log(pending); // undefined —— 还没赋值

// 访问不存在的属性
const obj = { name: "猪八戒" };
console.log(obj.age); // undefined —— age属性不存在

// 函数没有传参
function greet(name: string) {
    console.log(name); // 如果不传参,name是undefined
}
greet(); // undefined

// 函数没有返回值
function noReturn(): void {
    console.log("我只是打了个招呼");
}
const result = noReturn();
console.log(result); // undefined

2.3.2 为什么 null 和 undefined 同时存在

2.3.2.1 历史原因:null 来自 Java 的设计传统;undefined 是 JS 自己添加的

JavaScript早期借鉴了Java的设计,包括null的概念(Java里也有null)。但JavaScript后来发现需要区分"还没有赋值"和"已经明确为空"这两种情况,于是引入了undefined

graph TD
    A[JavaScript的设计] --> B[null]
    A --> C[undefined]
    B --> D["来自Java传统<br/>表示: 主动设置为空 "]
    C --> E["JavaScript自己添加<br/>表示: 未初始化/不存在"]

2.3.2.2 两者并存反映了 JS 早期设计的不一致性;现代 TS 项目通常倾向于统一使用 undefined(或通过 strictNullChecks 强制显式处理)

很多现代TypeScript项目会统一使用undefined来表示"空",避免null和undefined混用造成的混乱:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 推荐的实践:统一使用undefined
interface User {
    name: string;
    email?: string;  // 可选属性,等价于 email: string | undefined
}

// 或者显式写
interface User2 {
    name: string;
    email: string | undefined;
}

💡 经验之谈:在现代TypeScript项目中,推荐使用undefined来表示"空",避免使用null。这样可以减少混淆——undefined表示"还没值"或"没有这个属性",语义更清晰。


2.3.3 strictNullChecks 的意义

2.3.3.1 关闭时:所有类型默认可以赋值为 null/undefined

strictNullChecks: false时,TypeScript对null和undefined非常宽容——几乎所有类型都可以是null或undefined:

1
2
3
4
// strictNullChecks: false 时
let name: string = null;   // OK!
let age: number = undefined; // OK!
let isActive: boolean = null; // OK!

2.3.3.2 开启后(推荐):必须显式声明或使用联合类型

strictNullChecks: true时,TypeScript会严格要求你处理null和undefined:

1
2
3
4
5
6
// tsconfig.json
{
    "compilerOptions": {
        "strictNullChecks": true
    }
}
1
2
3
4
5
6
7
// strictNullChecks: true 时
let name: string = null;   // 错误!string不能是null
let age: number = undefined; // 错误!number不能是undefined

// 正确的写法:必须显式声明
let name: string | null = null;
let age: number | undefined = undefined;

开启strictNullChecks的好处:

  1. 强制你思考边界情况——当你声明一个可能是null/undefined的值时,你必须显式处理
  2. 减少运行时错误——很多Cannot read property 'xxx' of null的错误在编译时就能发现
  3. 代码更清晰——一看就知道哪些值可能是空的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// strictNullChecks: true 下,处理可能为null的值有两种常见方式:

// 方式1:使用条件判断(显式检查)
function getLength(str: string | null): number {
    if (str !== null) {
        return str.length; // TS知道str是string,可以安全访问
    }
    return 0;
}

// 方式2:使用可选链 + 空值合并(更简洁)
function getLength2(str: string | null): number {
    return str?.length ?? 0; // str为null时返回0
}

console.log(getLength("hello"));   // 5 —— 正常字符串
console.log(getLength(null));      // 0 —— null时返回0
console.log(getLength2("world"));  // 5 —— 正常字符串
console.log(getLength2(null));      // 0 —— null时返回0

2.3.4 可选链 ?. 与空值合并 ??

2.3.4.1 ?.:链上节点为 null/undefined 时返回 undefined

可选链(Optional Chaining)是ES2020引入的语法,它可以安全地访问深层嵌套的属性:

 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
interface Person {
    name: string;
    address?: {
        city: string;
        zip?: string;
    };
}

const person: Person = {
    name: "孙悟空",
    address: {
        city: "花果山"
    }
};

// 传统写法:需要一层层判断
function getCity(person: Person): string {
    if (person.address) {
        if (person.address.city) {
            return person.address.city;
        }
    }
    return "未知";
}

// 可选链写法:一气呵成
function getCity2(person: Person): string {
    return person.address?.city ?? "未知";
}

console.log(getCity(person));   // 花果山
console.log(getCity2({ name: "猪八戒" })); // 未知 —— 没有address

可选链也可以用于方法调用和数组访问:

1
2
3
4
5
6
7
8
// 方法调用
obj.method?.();  // 如果obj.method存在,调用它;否则返回undefined

// 数组访问
arr?.[0];        // 如果arr存在且不为null/undefined,访问第一个元素

// 动态属性
obj?.[key];      // 如果obj存在,访问动态属性key

2.3.4.2 ??:仅对 null/undefined 生效;|| 对所有假值生效

空值合并运算符(Nullish Coalescing)是ES2020引入的另一个语法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ??:只在值为null或undefined时使用默认值
let a = null ?? "默认值";    // "默认值"
let b = undefined ?? "默认值"; // "默认值"
let c = 0 ?? "默认值";       // 0 —— 0不是null/undefined
let d = "" ?? "默认值";      // "" —— 空字符串不是null/undefined
let e = false ?? "默认值";    // false —— false不是null/undefined

// ||:在值为任何"假值"时都使用默认值
let f = null || "默认值";    // "默认值"
let g = undefined || "默认值"; // "默认值"
let h = 0 || "默认值";        // "默认值" —— 0是假值!
let i = "" || "默认值";       // "默认值" —— 空字符串是假值!
let j = false || "默认值";    // "默认值" —— false是假值!

💡 什么时候用哪个:当你想区分"没有值(null/undefined)“和"有值但可能是假值(0、""、false)“时,用??。当你只关心"是不是假值"时,用||

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 典型场景:配置项
const config = {
    timeout: 0,         // 0ms超时(特殊含义:立即超时)
    retries: "",       // 重试次数为空(特殊含义:不重试)
    debug: false        // 调试模式关闭
};

// 用 ?? —— 保留0和""这些有效值
const timeout1 = config.timeout ?? 3000;  // 0 —— 保留有效值0
const retries1 = config.retries ?? 3;      // "" —— 保留有效值""

// 用 || —— 会错误地覆盖0和""
const timeout2 = config.timeout || 3000;  // 3000 —— 0被覆盖了!错误!
const retries2 = config.retries || 3;      // 3 —— ""被覆盖了!错误!

2.4 symbol 与 bigint

这两个类型虽然不常用,但在某些场景下非常重要。


2.4.1 symbol 类型

symbol是ES6引入的原始类型,代表一个唯一的、不可变的标识符

2.4.1.1 创建:const sym = Symbol('description'),每个 symbol 唯一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 创建symbol
const sym1 = Symbol("id");
const sym2 = Symbol("id");

console.log(sym1);        // Symbol(id)
console.log(sym2);        // Symbol(id)
console.log(sym1 === sym2); // false —— 每次创建的symbol都是唯一的!
console.log(typeof sym1);  // symbol

// symbol可以作为对象的键
const obj: { [key: symbol]: number } = {};
const myKey = Symbol("myKey");
obj[myKey] = 42;
console.log(obj[myKey]); // 42

2.4.1.2 Well-Known Symbols:Symbol.iterator、Symbol.toStringTag 等

JavaScript定义了一些内置的symbol,用于控制对象的行为:

 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
// Symbol.iterator:让对象可迭代
const collection = {
    items: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                if (index < this.items.length) {
                    return { value: this.items[index++], done: false };
                }
                return { value: undefined, done: true };
            }
        };
    }
};

for (const item of collection) {
    console.log(item); // 1, 2, 3
}

// Symbol.toStringTag:自定义对象的toString标签
const person = {
    name: "孙悟空",
    [Symbol.toStringTag]: "Person"
};
console.log(person.toString()); // [object Person]

// Symbol.hasInstance:自定义instanceof行为
class MyArray {
    static [Symbol.hasInstance](instance) {
        return Array.isArray(instance);
    }
}
console.log([] instanceof MyArray); // true

2.4.1.3 为什么需要 symbol:JavaScript 对象的键只能是字符串,存在字符串冲突风险

在ES6之前,对象的键只能是字符串。这会导致一些问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// JavaScript的对象键是字符串
const obj = {};
obj["name"] = "孙悟空";
obj["name"] = "猪八戒"; // 覆盖了!
console.log(obj.name); // "猪八戒"

// 使用symbol作为键可以避免冲突
const status = Symbol("status");
obj[status] = "active";
console.log(obj[status]); // "active"
console.log(obj["status"]); // undefined —— 不同的键!

symbol的应用场景:

  1. 定义类的私有成员(虽然不是真正的私有,但symbol键不会意外访问)
  2. 定义唯一常量
  3. 自定义迭代行为
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// symbol用于私有属性(命名约定,非真正私有)
const _name = Symbol("name");
const _age = Symbol("age");

class Person {
    [_name]: string;
    [_age]: number;

    constructor(name: string, age: number) {
        this[_name] = name;
        this[_age] = age;
    }

    greet() {
        console.log(`我是${this[_name]},今年${this[_age]}岁`);
    }
}

const person = new Person("孙悟空", 500);
person.greet(); // 我是孙悟空,今年500岁
// person._name 访问不到 —— _name是symbol类型

2.4.2 bigint 类型

bigint是ES2020引入的原始类型,用于表示比Number.MAX_SAFE_INTEGER还大的整数

2.4.2.1 字面量语法:100n,与 number 完全不兼容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// bigint字面量:在数字后面加n
const bigNumber = 9007199254740993n;
console.log(bigNumber); // 9007199254740993n
console.log(typeof bigNumber); // bigint

// bigint与number完全不兼容
let num: number = 42;
let big: bigint = 42n;

num = big;  // 错误!number和bigint不兼容
big = num;  // 错误!

// 可以显式转换
num = Number(big);  // OK
big = BigInt(num);  // OK

2.4.2.2 必要性:超过 Number.MAX_SAFE_INTEGER 的整数运算精度问题

JavaScript的number类型使用64位浮点数表示,整数的安全范围只有-(2^53-1)2^53-1,即从-Number.MAX_SAFE_INTEGER+Number.MAX_SAFE_INTEGER

1
2
3
4
5
6
7
8
9
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

// 超过安全范围的整数运算会丢失精度
console.log(9007199254740991 + 2); // 9007199254740992 —— 实际上应该是 9007199254740993!
console.log(9007199254740991 + 3); // 9007199254740994 —— 实际上应该是 9007199254740994!

// 使用bigint可以安全处理大整数
console.log(9007199254740991n + 2n); // 9007199254740993n —— 精确!
console.log(9007199254740991n + 3n); // 9007199254740994n —— 精确!

bigint支持的运算:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let a: bigint = 100n;
let b: bigint = 50n;

console.log(a + b);   // 150n —— 加法
console.log(a - b);   // 50n —— 减法
console.log(a * b);   // 5000n —— 乘法
console.log(a / b);   // 2n —— 除法(取整)
console.log(a % b);   // 0n —— 取余
console.log(a ** 2n); // 10000n —— 幂运算

// 比较运算:bigint支持大小比较和相等比较,都返回布尔值
console.log(a > b);      // true —— 大小比较(>)
console.log(a === b);     // false —— 相等比较(100n !== 50n)
console.log(a == b);      // false —— 宽松相等比较
console.log(a > 50n);     // true —— 可以和bigint字面量比较大小
console.log(10n === 10n); // true —— 相同的bigint字面量相等比较

2.5 typeof 运算符

typeof是JavaScript的一个运算符,用于在运行时获取值的类型。TypeScript对typeof进行了扩展,增加了在类型位置使用的功能。


2.5.1 运行时 typeof

2.5.1.1 返回 ‘string’、’number’、‘boolean’、‘object’、‘function’ 等

typeof在JavaScript中是一个一元运算符,用于获取值的运行时类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// typeof的基础用法
console.log(typeof "hello");      // "string"
console.log(typeof 123);         // "number"
console.log(typeof true);        // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);        // "object" —— 注意!这是JS的历史bug
console.log(typeof { name: "Tom" }); // "object"
console.log(typeof [1, 2, 3]);   // "object" —— 数组也是object
console.log(typeof function() {}); // "function"
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof 100n);        // "bigint"

2.5.1.2 注意:typeof null === ‘object’(JS 历史 bug,TS 不受影响)

这是JavaScript最著名的bug之一:

1
2
3
4
5
6
7
console.log(typeof null); // "object"

console.log(null instanceof Object); // false

// 在类型推断中,TypeScript不会把这个bug带进来
let empty: null = null;
console.log(typeof empty); // 在TS中,null的类型仍然是null,不是object

2.5.2 类型位置使用 typeof

2.5.2.1 type A = typeof someVariable

这是TypeScript的一个强大功能——可以用变量的值来推断类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 用typeof获取变量的类型
const person = {
    name: "孙悟空",
    age: 500,
    isMarried: false
};

type Person = typeof person;
// 注释:Person = { name: string; age: number; isMarried: boolean }

// 新变量可以直接用这个类型
const anotherPerson: Person = {
    name: "猪八戒",
    age: 3000,
    isMarried: true
};

console.log(anotherPerson.name); // 猪八戒

2.5.2.2 as const + typeof:获得最窄字面量类型

如果想获取最窄的类型(每个属性都是字面量类型),可以用as const

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 不使用as const
const status = "loading";
type Status = typeof status;
// 注释:Status = string —— 太宽泛了

// 使用as const
const statusConst = "loading" as const;
type StatusConst = typeof statusConst;
// 注释:StatusConst = "loading" —— 最窄的字面量类型!

// 用as const获取整个对象的字面量类型
const config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
} as const;

type Config = typeof config;
// 注释:Config = {
//          readonly apiUrl: "https://api.example.com";
//          readonly timeout: 5000;
//          readonly retries: 3;
//        }

这个技巧常用于定义配置对象和常量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 应用场景:定义路由路径
const ROUTES = {
    HOME: "/",
    ABOUT: "/about",
    USER_PROFILE: "/user/:id",
    SETTINGS: "/settings"
} as const;

type Route = typeof ROUTES[keyof typeof ROUTES];
// 注释:Route = "/" | "/about" | "/user/:id" | "/settings"

function navigate(route: Route) {
    console.log("跳转到:" + route);
}

navigate("/");              // OK
navigate("/user/123");       // OK —— 符合/user/:id模式
navigate("/invalid");        // 错误!不在允许的路由中

📝 本节小结typeof运算符在运行时返回JavaScript类型的字符串(‘string’、’number’、‘boolean’等),但注意typeof null === 'object'是JS的历史bug。TypeScript扩展了typeof,可以在类型位置使用——type A = typeof someVariable会获取变量的类型。配合as const使用可以获得最窄的字面量类型,这个技巧常用于定义配置对象、路由等常量。


本章小结

本章深入学习了TypeScript的类型基础,重点是7种原始类型和类型注解。

JavaScript的7种原始类型:string、number、bigint、boolean、undefined、null、symbol。它们是语言的最底层数据类型。其中nullundefined的语义不同——null表示"主动设置为空”,undefined表示"未赋值/不存在”。

TypeScript新增的类型:any、unknown、void、never(编译时存在,运行时擦除),以及enum和元组。

类型注解方式:显式注解(let x: number = 5)和隐式推断(let x = 5推断为number)。const声明会推断为字面量类型,let声明会推断为宽泛类型。

string/number/boolean的细节:字符串有字面量类型("Up" | "Down"),number包括NaN(类型仍是number),bigint用于大整数运算。boolean也有字面量类型true/false

null/undefined处理:推荐统一使用undefined,开启strictNullChecks强制显式处理。?.可选链安全访问深层属性,??空值合并只在值为null/undefined时使用默认值,不会被0、空字符串等假值误导。

symbol和bigint:symbol用于创建唯一标识符,可以作为对象键避免字符串冲突;bigint用于超过Number.MAX_SAFE_INTEGER的大整数,与number类型完全不兼容。

typeof运算符:运行时返回类型字符串,TypeScript中可在类型位置使用typeof variable获取变量类型,配合as const获取最窄字面量类型。

下一章我们将学习TypeScript的"特殊类型"——any、unknown、void、never,以及枚举和元组。这些类型虽然不像string/number那么常见,但在实际开发中扮演着重要角色。