符合

Symbol 是一个内置对象,其构造函数返回 symbol primitive(也称为符号值或只是符号),保证是唯一的。符号通常用于向对象添加唯一的属性键,这些属性键不会与任何其他代码可能添加到该对象的键发生冲突,并且对于其他代码通常用于访问该对象的任何机制都是隐藏的。这会启用弱 encapsulation 的形式,或弱 信息隐藏 的形式。

¥**Symbol** is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding.

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

¥Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for("key") call will always return the same Symbol for a given value of "key". When Symbol.for("key") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned.

描述

¥Description

要创建新的原始符号,请编写 Symbol() 并使用可选字符串作为其描述:

¥To create a new primitive Symbol, you write Symbol() with an optional string as its description:

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

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

¥The above code creates three new Symbols. Note that Symbol("foo") does not coerce the string "foo" into a Symbol. It creates a new Symbol each time:

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

以下带有 new 运算符的语法将抛出 TypeError

¥The following syntax with the new operator will throw a TypeError:

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

这会阻止作者创建显式 Symbol 封装器对象而不是新的 Symbol 值,并且可能会令人惊讶,因为通常可以围绕原始数据类型创建显式封装器对象(例如 new Booleannew Stringnew Number)。

¥This prevents authors from creating an explicit Symbol wrapper object instead of a new Symbol value and might be surprising as creating explicit wrapper objects around primitive data types is generally possible (for example, new Boolean, new String and new Number).

如果你确实想创建 Symbol 封装对象,可以使用 Object() 函数:

¥If you really want to create a Symbol wrapper object, you can use the Object() function:

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

由于符号是唯一具有引用标识的原始数据类型(即,你不能两次创建相同的符号),因此它们在某种程度上表现得像对象。例如,它们是可垃圾收集的,因此可以存储在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中。

¥Because symbols are the only primitive data type that has reference identity (that is, you cannot create the same symbol twice), they behave like objects in some way. For example, they are garbage collectable and can therefore be stored in WeakMap, WeakSet, WeakRef, and FinalizationRegistry objects.

全局符号注册表中的共享符号

¥Shared Symbols in the global Symbol registry

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

¥The above syntax using the Symbol() function will create a Symbol whose value remains unique throughout the lifetime of the program. To create Symbols available across files and even across realms (each of which has its own global scope), use the methods Symbol.for() and Symbol.keyFor() to set and retrieve Symbols from the global Symbol registry.

请注意,"全局符号注册表" 只是一个虚构的概念,可能不对应于 JavaScript 引擎中的任何内部数据结构 - 即使存在这样的注册表,它的内容也无法用于 JavaScript 代码,除非通过 for()keyFor() 方法。

¥Note that the "global Symbol registry" is only a fictitious concept and may not correspond to any internal data structure in the JavaScript engine — and even if such a registry exists, its content is not available to the JavaScript code, except through the for() and keyFor() methods.

方法 Symbol.for(tokenString) 接受一个字符串键并从注册表返回一个符号值,而 Symbol.keyFor(symbolValue) 接受一个符号值并返回与其对应的字符串键。每个都是另一个的逆,因此以下是 true

¥The method Symbol.for(tokenString) takes a string key and returns a symbol value from the registry, while Symbol.keyFor(symbolValue) takes a symbol value and returns the string key corresponding to it. Each is the other's inverse, so the following is true:

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

因为注册符号可以在任何地方任意创建,所以它们的行为几乎与它们封装的字符串完全相同。因此,不能保证它们是唯一的并且不可垃圾回收。因此,在 WeakMapWeakSetWeakRefFinalizationRegistry 对象中不允许使用注册符号。

¥Because registered symbols can be arbitrarily created anywhere, they behave almost exactly like the strings they wrap. Therefore, they are not guaranteed to be unique and are not garbage collectable. Therefore, registered symbols are disallowed in WeakMap, WeakSet, WeakRef, and FinalizationRegistry objects.

知名符号

¥Well-known Symbols

Symbol 构造函数的所有静态属性本身都是符号,其值在各个字段中都是恒定的。它们被称为众所周知的符号,其目的是充当某些内置 JavaScript 操作的 "protocols",允许用户自定义语言的行为。例如,如果构造函数有一个名称为 Symbol.hasInstance 的方法,则该方法将使用 instanceof 运算符对其行为进行编码。

¥All static properties of the Symbol constructor are Symbols themselves, whose values are constant across realms. They are known as well-known Symbols, and their purpose is to serve as "protocols" for certain built-in JavaScript operations, allowing users to customize the language's behavior. For example, if a constructor function has a method with Symbol.hasInstance as its name, this method will encode its behavior with the instanceof operator.

在众所周知的符号之前,JavaScript 使用普通属性来实现某些内置操作。例如,JSON.stringify 函数将尝试调用每个对象的 toJSON() 方法,而 String 函数将调用对象的 toString()valueOf() 方法。然而,随着越来越多的操作被添加到语言中,将每个操作指定为 "魔法属性" 可能会破坏向后兼容性,并使语言的行为更难以推断。众所周知的符号允许自定义为正常代码的 "invisible",通常仅读取字符串属性。

¥Prior to well-known Symbols, JavaScript used normal properties to implement certain built-in operations. For example, the JSON.stringify function will attempt to call each object's toJSON() method, and the String function will call the object's toString() and valueOf() methods. However, as more operations are added to the language, designating each operation a "magic property" can break backward compatibility and make the language's behavior harder to reason with. Well-known Symbols allow the customizations to be "invisible" from normal code, which typically only read string properties.

在 MDN 和其他来源中,众所周知的符号值通过前缀 @@ 进行风格化。例如,Symbol.hasInstance 写为 @@hasInstance。这是因为符号没有实际的文字格式,但使用 Symbol.hasInstance 并不反映使用其他别名来引用同一符号的能力。这就像 Function.name"Function" 之间的区别。

¥In MDN and other sources, well-known symbol values are stylized by prefixing @@. For example, Symbol.hasInstance is written as @@hasInstance. This is because symbols don't have actual literal formats, but using Symbol.hasInstance does not reflect the ability of using other aliases to refer to the same symbol. This is like the difference between Function.name and "Function".

众所周知的符号没有垃圾可回收性的概念,因为它们来自固定的集合,并且在程序的整个生命周期中都是唯一的,类似于 Array.prototype 等内在对象,因此它们也允许出现在 WeakMapWeakSetWeakRef、 和 FinalizationRegistry 个对象。

¥Well-known symbols do not have the concept of garbage collectability, because they come in a fixed set and are unique throughout the lifetime of the program, similar to intrinsic objects such as Array.prototype, so they are also allowed in WeakMap, WeakSet, WeakRef, and FinalizationRegistry objects.

查找对象的符号属性

¥Finding Symbol properties on objects

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

¥The method Object.getOwnPropertySymbols() returns an array of Symbols and lets you find Symbol properties on a given object. Note that every object is initialized with no own Symbol properties, so that this array will be empty unless you've set Symbol properties on the object.

构造函数

¥Constructor

Symbol()

创建一个新的 Symbol 对象。它不是传统意义上的构造函数,因为它只能作为函数调用,而不能用 new Symbol() 来构造。

静态属性

¥Static properties

静态属性都是众所周知的符号。在这些符号的描述中,我们将使用诸如“Symbol.hasInstance 是一个确定...的方法”之类的语言,但请记住,这是指以该符号作为方法名称的对象方法的语义(因为众所周知的符号充当 "protocols"),没有描述符号本身的价值。

¥The static properties are all well-known Symbols. In these Symbols' descriptions, we will use language like "Symbol.hasInstance is a method determining…", but bear in mind that this is referring to the semantic of an object's method having this Symbol as the method name (because well-known Symbols act as "protocols"), not describing the value of the Symbol itself.

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 环境绑定之外。

静态方法

¥Static methods

Symbol.for()

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

Symbol.keyFor()

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

实例属性

¥Instance properties

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

¥These properties are defined on Symbol.prototype and shared by all Symbol instances.

Symbol.prototype.constructor

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

Symbol.prototype.description

包含符号描述的只读字符串。

Symbol.prototype[@@toStringTag]

@@toStringTag 属性的初始值为字符串 "Symbol"。该属性在 Object.prototype.toString() 中使用。但是,由于 Symbol 也有自己的 toString() 方法,因此除非你使用符号 thisArg 调用 Object.prototype.toString.call(),否则不会使用此属性。

实例方法

¥Instance methods

Symbol.prototype.toString()

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

Symbol.prototype.valueOf()

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

Symbol.prototype[@@toPrimitive]()

返回符号。

示例

¥Examples

将 typeof 运算符与符号一起使用

¥Using the typeof operator with Symbols

typeof 运算符可以帮助你识别符号。

¥The typeof operator can help you to identify Symbols.

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

符号类型转换

¥Symbol type conversions

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

¥Some things to note when working with type conversion of Symbols.

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

符号和 for...in 迭代

¥Symbols and for...in iteration

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

¥Symbols are not enumerable in for...in iterations. In addition, Object.getOwnPropertyNames() will not return Symbol object properties, however, you can use Object.getOwnPropertySymbols() to get these.

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"

符号和 JSON.stringify()

¥Symbols and JSON.stringify()

使用 JSON.stringify() 时,符号键控属性将被完全忽略:

¥Symbol-keyed properties will be completely ignored when using JSON.stringify():

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

详细信息请参见 JSON.stringify()

¥For more details, see JSON.stringify().

作为属性键的符号封装对象

¥Symbol wrapper objects as property keys

当 Symbol 封装对象用作属性键时,该对象将被强制为其封装的 Symbol:

¥When a Symbol wrapper object is used as a property key, this object will be coerced to its wrapped Symbol:

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

规范

Specification
ECMAScript Language Specification
# sec-symbol-objects

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看