Symbol
Symbol
是一个内置对象,其构造函数返回一个 symbol
原始值——也称为 Symbol 值或仅 Symbol——保证是唯一的。Symbol 通常用于向对象添加唯一的属性键,这些键不会与任何其他代码可能添加到对象中的键冲突,并且对其他代码通常用于访问对象的任何机制都是隐藏的。这使得某种形式的弱 封装 或弱形式的 信息隐藏 成为可能。
每次 Symbol()
调用都保证返回一个唯一的 Symbol。对于给定的 "key"
值,每次 Symbol.for("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 可以任意地在任何地方创建,所以它们的行为几乎与它们包装的字符串完全相同。因此,它们不能保证是唯一的,也不能进行垃圾回收。因此,在 WeakMap
、WeakSet
、WeakRef
和 FinalizationRegistry
对象中不允许使用注册的 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
等内在对象,因此它们也允许在 WeakMap
、WeakSet
、WeakRef
和 FinalizationRegistry
对象中。
查找对象上的 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。
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";
Symbol 类型转换
在处理 Symbols 的类型转换时,需要注意一些事项。
- 尝试将 Symbol 转换为数字时,将抛出
TypeError
(例如+sym
或sym | 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()
来获取这些属性。
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 键属性。
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 语言规范 # sec-symbol-objects |
浏览器兼容性
BCD 表格仅在浏览器中加载
另请参阅
core-js
中Symbol
的 polyfilltypeof
- JavaScript 数据类型和数据结构
- ES6 深入:Symbols 在 hacks.mozilla.org 上 (2015)