Symbol

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

每次 Symbol() 调用都保证返回一个唯一的 Symbol。对于给定的 "key" 值,每次 Symbol.for("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 可以任意地在任何地方创建,所以它们的行为几乎与它们包装的字符串完全相同。因此,它们不能保证是唯一的,也不能进行垃圾回收。因此,在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中不允许使用注册的 Symbol。

众所周知的 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.asyncIterator

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

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 运算符与 Symbols

typeof运算符可以帮助您识别 Symbols。

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

Symbol 类型转换

在处理 Symbols 的类型转换时,需要注意一些事项。

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

Symbols 和 for...in 迭代

Symbols 在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"

Symbols 和 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 语言规范
# sec-symbol-objects

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅