let obj = { 0: "test" // 和 "0": "test" 一样 }; // 两个 alert 都访问相同的属性(Number 0 被转换为字符串 "0") alert( obj["0"] ); // test alert( obj[0] ); // test(同一个属性)
全局 symbol
正如我们所看到的,通常所有的 Symbol 都是不同的,即使它们有相同的名字。但有时我们想要名字相同的 Symbol 具有相同的实体。例如,应用程序的不同部分想要访问的 Symbol "id" 指的是完全相同的属性。
为了实现这一点,这里有一个 全局 Symbol 注册表。我们可以在其中创建 Symbol 并在稍后访问它们,它可以确保每次访问相同名字的 Symbol 时,返回的都是相同的 Symbol。
要从注册表中读取(不存在则创建)Symbol,请使用 Symbol.for(key)。
该调用会检查全局注册表,如果有一个描述为 key 的 Symbol,则返回该 Symbol,否则将创建一个新 Symbol(Symbol(key)),并通过给定的 key 将其存储在注册表中。
例如:
// 从全局注册表中读取 let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它 // 再次读取(可能是在代码中的另一个位置) let idAgain = Symbol.for("id"); // 相同的 Symbol alert( id === idAgain ); // true
注册表内的 Symbol 被称为 全局 Symbol。如果我们想要一个应用程序范围内的 Symbol,可以在代码中随处访问 —— 这就是它们的用途。
这听起来像 Ruby:
在一些编程语言中,例如 Ruby,每个名字都有一个 Symbol。
正如我们所看到的,在 JavaScript 中,全局 Symbol 也是这样的。
Symbol.keyFor
对于全局 Symbol,不仅有 Symbol.for(key) 按名字返回一个 Symbol,还有一个反向调用:Symbol.keyFor(sym),它的作用完全反过来:通过全局 Symbol 返回一个名字。
例如:
// 通过 name 获取 Symbol let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // 通过 Symbol 获取 name alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor 内部使用全局 Symbol 注册表来查找 Symbol 的键。所以它不适用于非全局 Symbol。如果 Symbol 不是全局的,它将无法找到它并返回 undefined。
也就是说,任何 Symbol 都具有 description 属性。
例如:
let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol alert( Symbol.keyFor(localSymbol) ); // undefined,非全局 alert( localSymbol.description ); // name
系统 Symbol
JavaScript 内部有很多“系统” Symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了表的规范中:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
……等等。
例如,Symbol.toPrimitive 允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的 Symbol 也会慢慢熟悉起来。
总结
Symbol 是唯一标识符的基本类型
Symbol 是使用带有可选描述(name)的 Symbol() 调用创建的。
Symbol 总是不同的值,即使它们有相同的名字。如果我们希望同名的 Symbol 相等,那么我们应该使用全局注册表:Symbol.for(key) 返回(如果需要的话则创建)一个以 key 作为名字的全局 Symbol。使用 Symbol.for 多次调用 key 相同的 Symbol 时,返回的就是同一个 Symbol。
Symbol 有两个主要的使用场景:
“隐藏” 对象属性。如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个 Symbol 并使用它作为属性的键。Symbol 属性不会出现在 for..in 中,因此它不会意外地被与其他属性一起处理。并且,它不会被直接访问,因为另一个脚本没有我们的 symbol。因此,该属性将受到保护,防止被意外使用或重写。
因此我们可以使用 Symbol 属性“秘密地”将一些东西隐藏到我们需要的对象中,但其他地方看不到它。