首页 / TypeScript 入门教程 / Symbol 与内置对象

TypeScript 入门教程

Symbol 与内置对象

本教程共 80 篇 · 第 76 篇 · 更新于 2026-08-10 · 约 13 分钟阅读

TypeScriptTypeScript 入门教程Symbolunique symbolMapSetWeakMapError

本节目标: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 symbolsymbol 的子类型,只能用 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 symbolsymbol 的字面量类型。每个 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,你才能区分 USDEUR 这两个 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

WeakMapWeakSet 的泛型参数只接受 object 类型,不能是 stringnumber 等原始类型。原因是它们的键必须是对对象的引用,才能实现弱引用。

而且它们不可迭代——没有 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" 之于 string
  • unique symbolconst 声明时自动产生,可用于名义类型模拟和精确的类型标签
  • Symbol 可以作为对象键,但 interface 的索引签名只能用 stringnumber
  • Map<K, V>get() 返回 T | undefined,必须处理键不存在的 case
  • Set<T> 实现 Iterable<T>,可以直接 for..of 遍历
  • WeakMapWeakSet 的泛型参数必须 extend object,且不可迭代
  • 自定义 Error 子类时,记得在 constructor 里设置 this.name
  • AggregateError 是 ES2021 引入的,可同时携带多个错误