符号
Baseline 广泛可用 *
Symbol 是一个内置对象,其构造函数返回一个 symbol 原始值——也称为 Symbol 值或简称 Symbol——它保证是独一无二的。Symbol 通常用于向对象添加唯一的属性键,这些键不会与其他代码可能添加到对象的键冲突,并且对其他代码通常用于访问对象的任何机制都是隐藏的。这使得一种弱形式的封装,或者一种弱形式的信息隐藏成为可能。
每次 Symbol() 调用都保证返回一个唯一的 Symbol。每次 Symbol.for("key") 调用都会针对给定 "key" 值始终返回相同的 Symbol。当调用 Symbol.for("key") 时,如果在全局 Symbol 注册表中找到具有给定键的 Symbol,则返回该 Symbol。否则,将创建一个新的 Symbol,以给定键添加到全局 Symbol 注册表,然后返回。
描述
要创建一个新的原始 Symbol,你可以使用 Symbol(),并可选地传入一个字符串作为其描述。
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");
上述代码创建了三个新的 Symbol。注意,Symbol("foo") 不会将字符串 "foo" 强制转换为 Symbol。它每次都会创建一个新的 Symbol。
Symbol("foo") === Symbol("foo"); // false
const sym = new Symbol(); // TypeError
这可以防止开发者创建显式的 Symbol 包装对象而不是新的 Symbol 值,这可能会让人感到惊讶,因为通常可以为原始数据类型创建显式的包装对象(例如,new Boolean、new String 和 new Number)。
如果你确实想创建一个 Symbol 包装对象,你可以使用 Object() 函数。
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"
因为 Symbol 是唯一具有引用标识(即,你不能两次创建相同的 Symbol)的原始数据类型,所以它们在某些方面表现得像对象。例如,它们是可以被垃圾回收的,因此可以存储在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 对象中。
全局 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
Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true
由于注册 Symbol 可以随意创建,它们几乎完全像它们包装的字符串一样。因此,它们不保证是唯一的,并且不能被垃圾回收。因此,注册 Symbol 不允许在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 对象中使用。
知名 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 等固有对象,因此它们也允许在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 对象中使用。
在对象上查找 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。
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";
Symbol 类型转换
在使用 Symbol 类型转换时需要注意的一些事项。
- 当尝试将 Symbol 转换为数字时,会抛出
TypeError(例如,+sym或sym | 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() 来获取这些属性。
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 键的属性将被完全忽略。
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'
欲了解更多详情,请参见 JSON.stringify()。
Symbol 包装对象作为属性键
当 Symbol 包装对象用作属性键时,此对象将被强制转换为其包装的 Symbol。
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // still 1
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-symbol-objects |
浏览器兼容性
加载中…
另见
core-js中的SymbolPolyfilltypeof- JavaScript 数据类型和数据结构
- 深入 ES6:Symbol (2015) - hacks.mozilla.org