一、symbol类型用于创建对象的唯一标识符。
二、“Symbol” 值表示唯一的标识符。
三、Symbol 保证是唯一的。即使我们创建了许多具有相同描述的 Symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。
【示例1】这里有两个描述相同的 Symbol —— 它们不相等:

  1. let id1 = Symbol("id");
  2. let id2 = Symbol("id");
  3. alert(id1 == id2); // false

四、如果你熟悉 Ruby 或者其他有 “Symbol” 的语言 —— 别被误导。JavaScript 的 Symbol 是不同的。
五、Symbol 不会被自动转换为字符串
1、JavaScript 中的大多数值都支持字符串的隐式转换。
【示例1】我们可以alert任何值,都可以生效。Symbol 比较特殊,它不会被自动转换。这个alert将会提示出错:

  1. let id = Symbol("id");
  2. alert(id); // 类型错误:无法将 Symbol 值转换为字符串。

2、这是一种防止混乱的“语言保护”,因为字符串和 Symbol 有本质上的不同,不应该意外地将它们转换成另一个。
3、如果我们真的想显示一个 Symbol,我们需要在它上面调用.toString(),如下所示:

  1. let id = Symbol("id");
  2. alert(id.toString()); // Symbol(id),现在它有效了

4、或者获取symbol.description属性,只显示描述(description):

  1. let id = Symbol("id");
  2. alert(id.description); // id

创建Symbol

一、Symbol 是使用带有可选描述(name)的Symbol()调用创建的

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

二、创建时,我们可以给 Symbol 一个描述(也称为 Symbol 名),这在代码调试时非常有用:

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

主要使用场景

“隐藏”属性

一、Symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
二、如果我们使用的是属于第三方代码的user对象,我们想要给它们添加一些标识符。
我们可以给它们使用 Symbol 键:

  1. let user = { // 属于另一个代码
  2. name: "John"
  3. };
  4. let id = Symbol("id");
  5. user[id] = 1;
  6. alert( user[id] ); // 我们可以使用 Symbol 作为键来访问数据

三、使用Symbol(“id”)作为键,比起用字符串”id”来有什么好处呢?
1、因为user对象属于其他的代码,那些代码也会使用这个对象,所以我们不应该在它上面直接添加任何字段,这样很不安全。但是你添加的 Symbol 属性不会被意外访问到,第三方代码根本不会看到它,所以使用 Symbol 基本上不会有问题。
2、另外,假设另一个脚本希望在user中有自己的标识符,以实现自己的目的。这可能是另一个 JavaScript 库,因此脚本之间完全不了解彼此。
(1)然后该脚本可以创建自己的Symbol(“id”),像这样:

  1. // ...
  2. let id = Symbol("id");
  3. user[id] = "Their id value";

(2)我们的标识符和它们的标识符之间不会有冲突,因为 Symbol 总是不同的,即使它们有相同的名字。
(3)但如果我们处于同样的目的,使用字符串”id”而不是用 symbol,那么就会出现冲突:

  1. let user = { name: "John" };
  2. // 我们的脚本使用了 "id" 属性。
  3. user.id = "Our id value";
  4. // ……另一个脚本也想将 "id" 用于它的目的……
  5. user.id = "Their id value"
  6. // 砰!无意中被另一个脚本重写了 id!

四、从技术上说,Symbol 不是 100% 隐藏的。
1、有一个内置方法Object.getOwnPropertySymbols(obj)允许我们获取所有的 Symbol。
2、还有一个名为Reflect.ownKeys(obj)的方法可以返回一个对象的所有键,包括 Symbol。所以它们并不是真正的隐藏。
3、但是大多数库、内置方法和语法结构都没有使用这些方法。

对象字面量中的 Symbol

一、如果我们要在对象字面量{…}中使用 Symbol,则需要使用方括号把它括起来。
【示例1】

  1. let id = Symbol("id");
  2. let user = {
  3. name: "John",
  4. [id]: 123 // 而不是 "id":123
  5. };

1、这是因为我们需要变量id的值作为键,而不是字符串 “id”。

Symbol 在 for…in 中会被跳过

一、Symbol 属性不参与for..in循环。
【示例1】

  1. let id = Symbol("id");
  2. let user = {
  3. name: "John",
  4. age: 30,
  5. [id]: 123
  6. };
  7. for (let key in user) alert(key); // name, age (no symbols)
  8. // 使用 Symbol 任务直接访问
  9. alert( "Direct: " + user[id] );

二、Object.keys(user)也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
三、相反,Object.assign会同时复制字符串和 symbol 属性:

  1. let id = Symbol("id");
  2. let user = {
  3. [id]: 123
  4. };
  5. let clone = Object.assign({}, user);
  6. alert( clone[id] ); // 123

1、这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个 object 时,通常希望所有属性被复制(包括像id这样的 Symbol)。

系统 Symbol

一、JavaScript 内部有很多“系统” Symbol,我们可以使用它们来微调对象的各个方面。
二、它们都被列在了众所周知的 Symbol表的规范中:

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

例如,Symbol.toPrimitive允许我们将对象描述为原始值转换。

全局 symbol

一、通常所有的 Symbol 都是不同的,即使它们有相同的名字。但有时我们想要名字相同的 Symbol 具有相同的实体。
例如,应用程序的不同部分想要访问的 Symbol”id”指的是完全相同的属性。
二、为了实现这一点,这里有一个全局 Symbol 注册表。我们可以在其中创建 Symbol 并在稍后访问它们,它可以确保每次访问相同名字的 Symbol 时,返回的都是相同的 Symbol。

Symbol.for(key)

一、要从注册表中读取(不存在则创建)Symbol,请使用Symbol.for(key)。
1、该调用会检查全局注册表,如果有一个描述为key的 Symbol,则返回该 Symbol,否则将创建一个新 Symbol(Symbol(key)),并通过给定的key将其存储在注册表中。

  1. // 从全局注册表中读取
  2. let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它
  3. // 再次读取(可能是在代码中的另一个位置)
  4. let idAgain = Symbol.for("id");
  5. // 相同的 Symbol
  6. alert( id === idAgain ); // true

2、注册表内的 Symbol 被称为全局 Symbol。如果我们想要一个应用程序范围内的 Symbol,可以在代码中随处访问 —— 这就是它们的用途。
二、在一些编程语言中,例如 Ruby,每个名字都有一个 Symbol。
1、正如我们所看到的,在 JavaScript 中,全局 Symbol 也是这样的。

Symbol.keyFor

一、对于全局 Symbol,不仅有Symbol.for(key)按名字返回一个 Symbol,还有一个反向调用:Symbol.keyFor(sym),它的作用完全反过来:通过全局 Symbol 返回一个名字。
【示例1】

  1. // 通过 name 获取 Symbol
  2. let sym = Symbol.for("name");
  3. let sym2 = Symbol.for("id");
  4. // 通过 Symbol 获取 name
  5. alert( Symbol.keyFor(sym) ); // name
  6. alert( Symbol.keyFor(sym2) ); // id

二、Symbol.keyFor内部使用全局 Symbol 注册表来查找 Symbol 的键。所以它不适用于非全局 Symbol。如果 Symbol 不是全局的,它将无法找到它并返回undefined。
1、也就是说,任何 Symbol 都具有description属性。

  1. let globalSymbol = Symbol.for("name");
  2. let localSymbol = Symbol("name");
  3. alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol
  4. alert( Symbol.keyFor(localSymbol) ); // undefined,非全局
  5. alert( globalSymbol.description); // name
  6. alert( localSymbol.description ); // name