Symbol 与内置对象
本教程共 80 篇 · 第 76 篇 · 更新于 2026-08-10 · 约 13 分钟阅读
本节目标:Symbol 不是字符串——它在 TS 里有自己独特的类型级别待遇(
unique symbol)。顺便搞清楚 Map、Set、WeakMap、WeakSet 这些 ES 内置对象在 TS 里怎么写类型。
Symbol 基础:值类型,不是对象
ES2015 引入了 symbol 作为第七种原始类型。每一个 Symbol() 调用都返回一个独一无二的值:
const s1 = Symbol("desc");
const s2 = Symbol("desc");
console.log(s1 === s2); // false —— 描述相同,但值不同
TypeScript 里,symbol 是一个原始类型:
let sym: symbol;
sym = Symbol(); // ✅
sym = Symbol("key"); // ✅
sym = "key"; // ❌ Type 'string' is not assignable to type 'symbol'
unique symbol:类型级别的”唯一”
unique symbol 是 symbol 的子类型,只能用 const 声明或 readonly 属性产生:
const Foo = Symbol("Foo");
// Foo 的类型是 typeof Foo,即 unique symbol
const Bar: unique symbol = Symbol("Bar");
// 显式标注 unique symbol
let Baz = Symbol("Baz");
// Baz 的类型是 symbol(不是 unique symbol)
区别是什么?unique symbol 是字面量类型——就像 "hello" 是 string 的字面量类型,unique symbol 是 symbol 的字面量类型。每个 unique symbol 都是独一无二的类型。
const A = Symbol("A");
const B = Symbol("B");
type TypeOfA = typeof A; // unique symbol (对应那个特定的 Symbol 值)
type TypeOfB = typeof B; // unique symbol (另一个)
let x: typeof A = A; // ✅
x = B; // ❌ Type 'typeof B' is not assignable to type 'typeof A'
因为两个 unique symbol 互不兼容,TS 能保证引用类型的精确性。
什么时候用 unique symbol
主要用在 “名义类型” 模拟和 discriminated union 中做精确的 tag:
const USD = Symbol("USD");
const EUR = Symbol("EUR");
interface Money {
amount: number;
currency: typeof USD | typeof EUR;
}
const wallet: Money = {
amount: 100,
currency: USD, // ✅
};
// ❌ 不能随便写一个 symbol
wallet.currency = Symbol("USD");
// Type 'symbol' is not assignable to type 'unique symbol | unique symbol'
有了 unique symbol,你才能区分 USD 和 EUR 这两个 symbol。普通 symbol 类型做不到,因为它们看起来都一样。
symbol 作为对象键
Symbol 可以当对象的键,用来避免属性名冲突。TS 对此有完整的类型支持:
const id = Symbol("id");
interface User {
[id]: number; // computed property,索引签名不能写 symbol
name: string;
}
const user: User = {
[id]: 1,
name: "Alice",
};
console.log(user[id]); // 1 —— 类型安全,TS 知道这个键存在
如果你用 interface,symbol 键只能通过 具体的 unique symbol 来声明,不能用 symbol 类型写索引签名:
// ❌ 不能这样写
interface BadMap {
[key: symbol]: string; // TS 报错:索引签名参数类型必须是 string 或 number
}
要用通用 symbol 键,只能用 type alias + 交叉类型,或者用 Map<symbol, string>。
Symbol.iterator 等内置 well-known symbol
TypeScript 的 lib.es2015.symbol.wellknown.d.ts 定义了所有内置 Symbol 的类型。你不需要手动声明这些:
// 这些 Symbol 都是 TS 内置的 unique symbol
Symbol.iterator; // unique symbol
Symbol.asyncIterator; // unique symbol(ES2018)
Symbol.toStringTag; // unique symbol
Symbol.hasInstance; // unique symbol
所以你写 [Symbol.iterator]() 时,TS 能认出这是一个特殊的迭代器方法,而不只是一个普通的 symbol key。
Map<K, V> 类型
Map 是 ES2015 引入的键值对集合,键可以是任意值(不像对象只能用 string/symbol):
const map = new Map<string, number>();
map.set("a", 1);
map.set("b", 2);
const value = map.get("a");
// value 类型:number | undefined(因为键可能不存在)
console.log(map.has("c")); // false
console.log(map.size); // 2
Map.get() 返回 T | undefined 是个常见的坑——你必须处理键不存在的情况:
const count = map.get("unknown");
if (count !== undefined) {
console.log(count * 2); // 收窄后 count: number
}
Map 的遍历类型
const map = new Map<string, number>([
["x", 10],
["y", 20],
]);
for (const [key, value] of map) {
// key: string, value: number
console.log(`${key} -> ${value}`);
}
// keys() 返回 IterableIterator<string>
// values() 返回 IterableIterator<number>
// entries() 返回 IterableIterator<[string, number]>
const keys = [...map.keys()]; // string[]
const values = [...map.values()]; // number[]
Map 本身就是 Iterable<[K, V]>,所以 for..of 和展开运算符都能正确推断。
Set<T> 类型
Set 是唯一值集合:
const set = new Set<string>();
set.add("apple");
set.add("banana");
set.add("apple"); // 重复,不会添加
console.log(set.size); // 2
console.log(set.has("apple")); // true
const numbers = new Set([1, 2, 3, 4]);
const arr = [...numbers]; // number[]
const doubled = [...numbers].map((n) => n * 2); // n: number
for (const n of numbers) {
console.log(n); // n: number
}
Set<T> 实现了 Iterable<T>,迭代器产出 T。
WeakMap 和 WeakSet
这两个是”弱引用”版本,键必须是对象,不阻止垃圾回收:
// WeakMap<Key extends object, V>
const weakMap = new WeakMap<object, string>();
const obj1 = { id: 1 };
const obj2 = { id: 2 };
weakMap.set(obj1, "data for obj1");
weakMap.set(obj2, "data for obj2");
console.log(weakMap.get(obj1)); // "data for obj1"
console.log(weakMap.has(obj2)); // true
// WeakSet<T extends object>
const weakSet = new WeakSet<object>();
const a = {};
const b = {};
weakSet.add(a);
weakSet.add(b);
console.log(weakSet.has(a)); // true
Important
WeakMap和WeakSet的泛型参数只接受object类型,不能是string、number等原始类型。原因是它们的键必须是对对象的引用,才能实现弱引用。
而且它们不可迭代——没有 size 属性,不能用 for..of。这是刻意设计的,因为弱引用集合的内容随时可能被 GC 回收。
Error 类的类型
Error 是 JS 的内置类,TS 里有完整的类型定义:
// 内置 Error 类(简化版)
interface Error {
name: string;
message: string;
stack?: string;
}
interface ErrorConstructor {
new (message?: string): Error;
(message?: string): Error;
}
基本用法:
const err = new Error("something went wrong");
// err: Error
console.log(err.message); // "something went wrong"
console.log(err.name); // "Error"
console.log(err.stack); // string | undefined(调用栈)
TS 还内置了几个 Error 子类,各有特定的 name:
new TypeError("not a number"); // name: "TypeError"
new RangeError("out of range"); // name: "RangeError"
new SyntaxError("bad syntax"); // name: "SyntaxError"
new ReferenceError("not defined"); // name: "ReferenceError"
自定义 Error 子类的类型
class ValidationError extends Error {
constructor(
message: string,
public field: string,
public value: unknown
) {
super(message);
this.name = "ValidationError";
}
}
const err = new ValidationError("不能为空", "username", "");
// err: ValidationError
console.log(err.field); // "username"
console.log(err instanceof ValidationError); // true
AggregateError
ES2021 引入的 AggregateError,用于同时报告多个错误:
const errors = [
new Error("第一个错误"),
new TypeError("第二个错误"),
];
const agg = new AggregateError(errors, "多个操作失败");
// agg: AggregateError
console.log(agg.message); // "多个操作失败"
console.log(agg.errors); // readonly Error[]
for (const e of agg.errors) {
console.log(e.message);
}
AggregateError 的泛型参数只接受 Error 类型数组,类型检查不用额外配置。
小结
symbol是原始类型,unique symbol是它的字面量类型,类似于"hello"之于stringunique symbol用const声明时自动产生,可用于名义类型模拟和精确的类型标签- Symbol 可以作为对象键,但 interface 的索引签名只能用
string或number Map<K, V>的get()返回T | undefined,必须处理键不存在的 caseSet<T>实现Iterable<T>,可以直接for..of遍历WeakMap和WeakSet的泛型参数必须 extendobject,且不可迭代- 自定义 Error 子类时,记得在
constructor里设置this.name AggregateError是 ES2021 引入的,可同时携带多个错误