Symbol 类型

Symbol 类型

根据规范,对象的属性键只能是 String 类型或者 Symbol 类型。不是 Number,也不是 Boolean,只有 String 或 Symbol 这两种类型。git

到目前为止,咱们只见过 String。如今咱们来看看 Symbol 能给咱们带来什么好处。github

Symbol

"Symbol" 值表示惟一的标识符。编程

可使用 Symbol() 来建立这种类型的值:编程语言

// id 是 symbol 的一个实例化对象
let id = Symbol();

咱们能够给 Symbol 一个描述(也称为 Symbol 名),这对于调试很是有用:设计

// id 是描述为 "id" 的 Symbol
let id = Symbol("id");

Symbol 保证是惟一的。即便咱们建立了许多具备相同描述的 Symbol,它们的值也是不一样。描述只是一个不影响任何东西的标签。调试

例如,这里有两个描述相同的 Symbol —— 它们不相等:code

let id1 = Symbol("id");
let id2 = Symbol("id");

*!*
alert(id1 == id2); // false
*/!*

若是您熟悉 Ruby 或者其余有 "Symbol" 的语言 —— 别被误导。JavaScript 的 Symbol 不同凡响。对象

JavaScript 中的大多数值都支持 string 的隐式转换。例如,咱们能够 `alert` 任何值,这会起做用。Symbol 是特别的,它没法自动转换。

例如,这个 `alert` 将会显示错误:

let id = Symbol("id");
!
alert(id); // 类型错误:没法将 Symbol 值转换为 String。
/!教程

若是咱们真的想显示一个 Symbol,咱们须要在它上面调用 `.toString()`,以下所示:

let id = Symbol("id");
!
alert(id.toString()); // Symbol(id),如今它起做用了
/!ip

这是一种防止混乱的“语言保护”,由于 String 和 Symbol 有本质上的不一样,并且不该该偶尔将它们相互转化。

“隐藏”属性

Symbol 容许咱们建立对象的“隐藏”属性,代码的任何其余部分都不能偶尔访问或重写这些属性。

例如,若是咱们想存储 object user 的“标识符”,咱们可使用 Symbol 做为它的键:

let user = { name: "John" };
let id = Symbol("id");

user[id] = "ID Value";
alert( user[id] ); // 咱们可使用 Symbol 做为键来访问数据。

在 string "id" 上使用 Symbol("id") 有什么好处?

咱们用更深刻一点的示例来讲明这一点。

假设另外一个脚本但愿 user 中有它本身的 "id" 属性能够操做。这多是另外一个 JavaScript 库,因此这些脚本彻底不知道对方是谁。

而后该脚本能够建立本身的 Symbol("id"),以下所示:

// ...
let id = Symbol("id");

user[id] = "Their id value";

不会冲突,由于 Symbol 老是不一样的,即便它们有相同的名称。

如今请注意,若是咱们使用 String "id" 而不是用 symbol,那么就会出现冲突:

let user = { name: "John" };

//咱们的脚本使用 "id" 属性。
user.id = "ID Value";

// ...若是以后另外一个脚本为其目的使用 "id"...

user.id = "Their id value"
// 砰!无心中重写了 id!他不是故意伤害同事的,而是这样作了!

字面量中的 Symbol

若是咱们要在 object 字面量中使用 Symbol,则须要方括号。

就像这样:

let id = Symbol("id");

let user = {
  name: "John",
*!*
  [id]: 123 // 不只仅是 "id:123"
*/!*
};

这是由于咱们须要变量 id 的值做为键,而不是 String "id"。

Symbol 在 for..in 中被跳过

Symbolic 属性不参与 for..in 循环。

例如:

let id = Symbol("id");
let user = {
  name: "John",
  age: 30,
  [id]: 123
};

*!*
for (let key in user) alert(key); // name, age (no symbols)
*/!*

// 被 Symbol 任务直接访问
alert( "Direct: " + user[id] );

这是通常“隐藏”概念的一部分。若是另外一个脚本或库在咱们的对象上循环,它不会访问一个 Symbol 类型的属性。

相反,Object.assign 同时复制字符串和符号属性:

let id = Symbol("id");
let user = {
  [id]: 123
};

let clone = Object.assign({}, user);

alert( clone[id] ); // 123

这里并不矛盾,就是这样设计的。咱们的想法是当咱们克隆一个 object 或合并 object 时,一般但愿全部属性被复制(包括像 id 这样的 Symbol)。

咱们只能在对象中使用 string 或 symbol 做为键,其它类型转换为 String。

例如,在做为属性键使用时,数字 `0`变成了字符串 `"0"`:

let obj = {
0: "test" // same as "0": "test"
};

//两个 alert 都访问相同的属性(Number 0 被转换为 String "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (相同属性)

全局 symbol

正如咱们所看到的,一般全部的 Symbol 都是不一样的,即便它们有相同的名字。但有时咱们想要同一个名字的 Symbol 是相同的实体。

好比,咱们但愿在应用程序的不一样部分访问相同的 Symbol "id" 属性。

为此,存在一个全局 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,每一个名称都有一个 symbol。

在 JavaScript 中,咱们应该用全局 symbol。

Symbol.keyFor

对于全局 symbol,Symbol.for(key) 不只按名称返回一个 symbol,并且还有一个反向调用:Symbol.keyFor(sym),反过来:经过全局 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

例如:

alert( Symbol.keyFor(Symbol.for("name")) ); // name, 全局 Symbol

alert( Symbol.keyFor(Symbol("name2")) ); // undefined, 参数不是一个全局 Symbol

系统 Symbol

JavaScript 内部存在不少“系统” Symbol,咱们可使用它们来微调对象的各个方面。

它们列在熟悉的 Symbol 表的规范中:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • ...等等。

例如,Symbol.toPrimitive 容许咱们将对象描述为原始值转换,咱们很快就会看到它的使用。

当咱们研究相应的语言特征时,其余 Symbol 也会变得熟悉起来。

总结

Symbol 是惟一标识符的基本类型

Symbol 使用 Symbol() 建立的,调用带有一个可选的描述。

Symbol 老是不一样的值,即便它们有相同的名称。若是咱们但愿同名 Symbol 相等,那么咱们应该使用全局注册表:Symbol.for(key) 返回(若是须要的话建立)一个以 key 做为名称的全局 Symbol。Symbol.for 的屡次调用彻底返回相同的 Symbol。

Symbol 有两个主要的使用场景:

  1. “隐藏” 对象属性。若是须要将属性添加到 “属于” 另外一个脚本或库的对象中,则能够建立 Symbol 并将其用做属性键。Symbol 属性不出如今 for..in中,所以不会无意列出。另外,它不会被直接访问,由于另外一个脚本没有咱们的符号,因此它不会不当心干预它的操做。

    所以咱们可使用 Symbol 属性“秘密地”将一些东西隐藏到咱们须要的对象中,但其余人不会以对象属性的形式看到它。

  2. JavaScript 使用了许多系统 Symbol,这些 Symbol 能够做为 Symbol.* 访问。咱们可使用它们来改变一些内置行为。例如,在本教程的后面部分,咱们将使用 Symbol.iterator迭代Symbol.toPrimitive 来设置对象原始值的转换等等。

从技术上说,Symbol 不是 100% 隐藏的。有一个内置方法 Object.getOwnPropertySymbols(obj) 容许咱们获取全部的 Symbol。还有一个名为 Reflect.ownKeys(obj) 返回全部键,包括 Symbol。因此它们不是真正的隐藏。可是大多数库、内置方法和语法结构都遵循一个共同的协议。而明确调用上述方法的人可能很清楚他在作什么。

相关文章
相关标签/搜索