Symbol (符号)是ECMAScript 6新增的数据类型。符号是原始 值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用 唯一标识符,不会发生属性冲突的危险。
尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为Object API提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字 符串形式的对象属性。

1. 符号的基本用法

符号需要使用 Symbol() 函数初始化。因为符号本身是原始类 型,所以 typeof 操作符对符号返回 symbol 。

  1. let sym = Symbol();
  2. console.log(typeof sym); // symbol

调用 Symbol() 函数时,也可以传入一个字符串参数作为对符 号的描述(description),将来可以通过这个字符串来调试代码。 但是,这个字符串参数与符号定义或标识完全无关:

  1. let genericSymbol = Symbol();
  2. let otherGenericSymbol = Symbol();
  3. let fooSymbol = Symbol('foo');
  4. let otherFooSymbol = Symbol('foo');
  5. console.log(genericSymbol ==otherGenericSymbol); // false
  6. console.log(fooSymbol == otherFooSymbol);// false

符号没有字面量语法,这也是它们发挥作用的关键。按照规范, 你只要创建 Symbol() 实例并将其用作对象的新属性,就可以 保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属 性。

  1. let genericSymbol = Symbol();
  2. console.log(genericSymbol); // Symbol()
  3. let fooSymbol = Symbol('foo');
  4. console.log(fooSymbol); // Symbol(foo);

不能用作构造函数

最重要的是, Symbol() 函数不能用作构造函数,与 new 关键 字一起使用。这样做是为了避免创建符号包装对象,像使用 Boolean 、 String 或 Number 那样,它们都支持构造函数 且可用于初始化包含原始值的包装对象:

  1. let myBoolean = new Boolean();
  2. console.log(typeof myBoolean); // "object"
  3. let myString = new String();
  4. console.log(typeof myString); // "object"
  5. let myNumber = new Number();
  6. console.log(typeof myNumber); // "object"
  7. let mySymbol = new Symbol(); // TypeError:Symbol is not a constructor

如果你确实想使用符号包装对象,可以借用 Object() 函数:

  1. let mySymbol = Symbol();
  2. let myWrappedSymbol = Object(mySymbol);
  3. console.log(typeof myWrappedSymbol); //"object"

2. Symbol.for() 使用全局符号注册表

如果运行时的不同部分需要共享和重用符号实例,那么可以用一 个字符串作为键,在全局符号注册表中创建并重用符号。 为此,需要使用 Symbol.for() 方法:

  1. let fooGlobalSymbol = Symbol.for('foo');
  2. console.log(typeof fooGlobalSymbol); //symbol

Symbol.for() 对每个字符串键都执行幂等操作。第一次使用 某个字符串调用时,它会检查全局运行时注册表,发现不存在对 应的符号,于是就会生成一个新符号实例并添加到注册表中。后 续使用相同字符串的调用同样会检查注册表,发现存在与该字符 串对应的符号,然后就会返回该符号实例。

  1. let fooGlobalSymbol = Symbol.for('foo');
  2. // 创建新符号
  3. let otherFooGlobalSymbol = Symbol.for('foo');
  4. // 重用已有符号
  5. console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

即使采用相同的符号描述,在全局注册表中定义的符号跟使用 Symbol() 定义的符号也并不等同:

  1. let localSymbol = Symbol('foo');
  2. let globalSymbol = Symbol.for('foo');
  3. console.log(localSymbol === globalSymbol); //false

全局注册表中的符号必须使用字符串键来创建,因此作为参数传 给 Symbol.for() 的任何值都会被转换为字符串。此外,注册 表中使用的键同时也会被用作符号描述。

  1. let emptyGlobalSymbol = Symbol.for();
  2. console.log(emptyGlobalSymbol); //Symbol(undefined)

Symbol.keyFor() 查询全局注册表

还可以使用 Symbol.keyFor() 来查询全局注册表,这个方法 接收符号,返回该全局符号对应的字符串键。如果查询的不是全 局符号,则返回 undefined 。

  1. // 创建全局符号
  2. let s = Symbol.for('foo');
  3. console.log(Symbol.keyFor(s)); // foo
  4. // 创建普通符号
  5. let s2 = Symbol('bar');
  6. console.log(Symbol.keyFor(s2)); // undefined