符号

Baseline 广泛可用 *

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 2015 年 9 月以来,该特性已在各大浏览器中可用。

* 此特性的某些部分可能存在不同级别的支持。

Symbol 是一个内置对象,其构造函数返回一个 symbol 原始值——也称为 Symbol 值或简称 Symbol——它保证是独一无二的。Symbol 通常用于向对象添加唯一的属性键,这些键不会与其他代码可能添加到对象的键冲突,并且对其他代码通常用于访问对象的任何机制都是隐藏的。这使得一种弱形式的封装,或者一种弱形式的信息隐藏成为可能。

每次 Symbol() 调用都保证返回一个唯一的 Symbol。每次 Symbol.for("key") 调用都会针对给定 "key" 值始终返回相同的 Symbol。当调用 Symbol.for("key") 时,如果在全局 Symbol 注册表中找到具有给定键的 Symbol,则返回该 Symbol。否则,将创建一个新的 Symbol,以给定键添加到全局 Symbol 注册表,然后返回。

描述

要创建一个新的原始 Symbol,你可以使用 Symbol(),并可选地传入一个字符串作为其描述。

js
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");

上述代码创建了三个新的 Symbol。注意,Symbol("foo") 不会将字符串 "foo" 强制转换为 Symbol。它每次都会创建一个新的 Symbol。

js
Symbol("foo") === Symbol("foo"); // false

使用 new 操作符的以下语法将抛出 TypeError

js
const sym = new Symbol(); // TypeError

这可以防止开发者创建显式的 Symbol 包装对象而不是新的 Symbol 值,这可能会让人感到惊讶,因为通常可以为原始数据类型创建显式的包装对象(例如,new Booleannew Stringnew Number)。

如果你确实想创建一个 Symbol 包装对象,你可以使用 Object() 函数。

js
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"

因为 Symbol 是唯一具有引用标识(即,你不能两次创建相同的 Symbol)的原始数据类型,所以它们在某些方面表现得像对象。例如,它们是可以被垃圾回收的,因此可以存储在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中。

全局 Symbol 注册表中的共享 Symbol

上述使用 Symbol() 函数的语法将创建一个 Symbol,其值在程序生命周期内保持唯一。要创建跨文件甚至跨领域(每个领域都有自己的全局作用域)可用的 Symbol,请使用 Symbol.for()Symbol.keyFor() 方法从全局 Symbol 注册表中设置和检索 Symbol。

请注意,“全局 Symbol 注册表”只是一个虚拟概念,可能不对应于 JavaScript 引擎中的任何内部数据结构——即使存在这样的注册表,其内容也无法通过 JavaScript 代码访问,除了通过 for()keyFor() 方法。

方法 Symbol.for(tokenString) 接受一个字符串键并从注册表中返回一个 Symbol 值,而 Symbol.keyFor(symbolValue) 接受一个 Symbol 值并返回与之对应的字符串键。两者互为逆运算,因此以下结果为 true

js
Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true

由于注册 Symbol 可以随意创建,它们几乎完全像它们包装的字符串一样。因此,它们不保证是唯一的,并且不能被垃圾回收。因此,注册 Symbol 不允许在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中使用。

知名 Symbol

Symbol 构造函数的所有静态属性本身都是 Symbol,它们的值在不同领域是常量。它们被称为 知名 Symbol,其目的是充当某些内置 JavaScript 操作的“协议”,允许用户自定义语言的行为。例如,如果一个构造函数有一个以 Symbol.hasInstance 为名称的方法,这个方法将使用 instanceof 操作符来编码其行为。

在知名 Symbol 之前,JavaScript 使用普通属性来实现某些内置操作。例如,JSON.stringify 函数会尝试调用每个对象的 toJSON() 方法,String 函数会调用对象的 toString()valueOf() 方法。然而,随着语言中添加了更多的操作,将每个操作指定为“魔术属性”可能会破坏向后兼容性,并使语言行为更难理解。知名 Symbol 允许自定义对普通代码“不可见”,这些代码通常只读取字符串属性。

注意:规范过去使用 @@<symbol-name> 符号来表示知名 Symbol。例如,Symbol.hasInstance 写成 @@hasInstance,而 Array.prototype[Symbol.iterator]() 方法则被称为 Array.prototype[@@iterator]()。这种符号在规范中不再使用,但你可能仍然在旧文档或讨论中看到它。

知名 Symbol 没有垃圾回收的概念,因为它们以固定集合存在,并且在程序的整个生命周期内都是唯一的,类似于 Array.prototype 等固有对象,因此它们也允许在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中使用。

在对象上查找 Symbol 属性

方法 Object.getOwnPropertySymbols() 返回一个 Symbol 数组,允许你在给定对象上查找 Symbol 属性。请注意,每个对象在初始化时都没有自己的 Symbol 属性,因此除非你已在该对象上设置了 Symbol 属性,否则此数组将为空。

构造函数

Symbol()

返回 Symbol 类型的原始值。使用 new 调用时会抛出错误。

静态属性

静态属性都是知名 Symbol。在这些 Symbol 的描述中,我们将使用“Symbol.hasInstance 是一个方法,用于确定……”之类的语言,但请记住,这指的是一个对象的方法以这个 Symbol 作为方法名称的语义(因为知名 Symbol 充当“协议”),而不是描述 Symbol 值本身。

Symbol.asyncDispose

当对象超出作用域时,异步释放对象资源的方法。由 await using 声明使用。

Symbol.asyncIterator

返回对象默认 AsyncIterator 的方法。由 for await...of 使用。

Symbol.dispose

当对象超出作用域时,释放对象资源的方法。由 using 声明使用。

Symbol.hasInstance

一个确定构造函数对象是否将其识别为其实例的方法。由 instanceof 使用。

Symbol.isConcatSpreadable

一个布尔值,指示对象是否应被展平为其数组元素。由 Array.prototype.concat() 使用。

Symbol.iterator

返回对象默认迭代器的方法。由 for...of 使用。

Symbol.match

与字符串匹配的方法,也用于确定对象是否可用作正则表达式。由 String.prototype.match() 使用。

Symbol.matchAll

返回一个迭代器的方法,该迭代器生成正则表达式与字符串的匹配项。由 String.prototype.matchAll() 使用。

Symbol.replace

替换字符串中匹配的子字符串的方法。由 String.prototype.replace() 使用。

Symbol.search

返回字符串中与正则表达式匹配的索引的方法。由 String.prototype.search() 使用。

Symbol.species

用于创建派生对象的构造函数。

Symbol.split

在与正则表达式匹配的索引处分割字符串的方法。由 String.prototype.split() 使用。

Symbol.toPrimitive

将对象转换为原始值的方法。

Symbol.toStringTag

用于对象默认描述的字符串值。由 Object.prototype.toString() 使用。

Symbol.unscopables

一个对象值,其自身和继承的属性名称被排除在关联对象的 with 环境绑定之外。

静态方法

Symbol.for()

在全局 Symbol 注册表中搜索具有给定 key 的现有注册 Symbol,如果找到则返回。否则,将创建一个新的 Symbol 并以 key 注册。

Symbol.keyFor()

从全局 Symbol 注册表中检索给定 Symbol 的共享 Symbol 键。

实例属性

这些属性定义在 Symbol.prototype 上,并由所有 Symbol 实例共享。

Symbol.prototype.constructor

创建实例对象的构造函数。对于 Symbol 实例,初始值是 Symbol 构造函数。

Symbol.prototype.description

一个只读字符串,包含 Symbol 的描述。

Symbol.prototype[Symbol.toStringTag]

[Symbol.toStringTag] 属性的初始值是字符串 "Symbol"。此属性在 Object.prototype.toString() 中使用。然而,由于 Symbol 也有其自己的 toString() 方法,除非你以 Symbol 作为 thisArg 调用 Object.prototype.toString.call(),否则此属性不会被使用。

实例方法

Symbol.prototype.toString()

返回一个包含 Symbol 描述的字符串。覆盖 Object.prototype.toString() 方法。

Symbol.prototype.valueOf()

返回 Symbol。覆盖 Object.prototype.valueOf() 方法。

Symbol.prototype[Symbol.toPrimitive]()

返回 Symbol。

示例

将 typeof 操作符与 Symbol 一起使用

typeof 操作符可以帮助你识别 Symbol。

js
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";

Symbol 类型转换

在使用 Symbol 类型转换时需要注意的一些事项。

  • 当尝试将 Symbol 转换为数字时,会抛出 TypeError(例如,+symsym | 0)。
  • 使用松散相等时,Object(sym) == sym 返回 true
  • Symbol("foo") + "bar" 会抛出 TypeError(无法将 Symbol 转换为字符串)。这可以防止你悄悄地从 Symbol 创建新的字符串属性名,例如。
  • “更安全”的 String(sym) 转换 与对 Symbol 调用 Symbol.prototype.toString() 类似,但请注意 new String(sym) 会抛出错误。

Symbol 和 for...in 迭代

Symbol 在 for...in 迭代中不可枚举。此外,Object.getOwnPropertyNames() 不会返回 Symbol 对象属性,但是,你可以使用 Object.getOwnPropertySymbols() 来获取这些属性。

js
const obj = {};

obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";

for (const i in obj) {
  console.log(i);
}
// "c" "d"

Symbol 和 JSON.stringify()

使用 JSON.stringify() 时,Symbol 键的属性将被完全忽略。

js
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'

欲了解更多详情,请参见 JSON.stringify()

Symbol 包装对象作为属性键

当 Symbol 包装对象用作属性键时,此对象将被强制转换为其包装的 Symbol。

js
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // still 1

规范

规范
ECMAScript® 2026 语言规范
# sec-symbol-objects

浏览器兼容性

另见