Symbol() 是个什么东西?

最近在一些 Demo 中经常会看到 Symbol() 这个东西,例如:

var race = {
  protoss: Symbol(),
  terran: Symbol(),
  zerg: Symbol()
}

就只能记得大概了,就是赋值一个独特的值,但是这个值具体是什么我想不起来了。
所以还是记个笔记加深下印象,省的看了又忘。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型

如果只看 阮一峰的 ECMAScript 6入门 和 MDN 上的说明,可能会有点迷糊,但是大概知道只要是 Symbol类型 是都是独一无二的,保证不会与其他属性名产生冲突。
例如:

var s1 = Symbol(1)
// Symbol(1)
var s2 = Symbol(1)
// Symbol(1)
s1 === s2
// false
s1 == s2
// false

所以,就出现了文章一开始的那个例子,出自方应杭老师的的每日一题(在文章最后有附链接)
大概的需求场景,就是需要通过传入不同的 String 来执行不同的操作,接下来我就大概引用该文章内的例子来说明

例如:在玩SC2的时候,我们需要在开始时选择种族,选择完成之后在游戏加载时就会执行对应的创建角色操作。

var race = {
  protoss: 'protoss', // 神族
  terran: 'terran', // 人族
  zerg: 'zerg' // 虫族
}
function createRole(type){
  if(type === race.protoss){ 创建神族角色 }
  else if(type === race.terran){ 创建人族角色 }
  else if(type === race.zerg){ 创建虫族角色 }
}

用户选择种族后,就需要调用 createRole 来创建角色:

// 传入字符串
createRole('zerg') 
// 或者传入变量
createRole(race.zerg)

但是在传入 type 值的时候,如果直接用字符串,就不是一个好的方式(不利于将来的修改和维护),所以需要传入例如 race.zerg 这样的变量属性
那么如果这样操作的话,其实这些对象属性(race.protoss,race.terran,race.zerg)对应的值并不重要,因为传入的肯定是同一个值。
所以以下声明和前一个例子中声明可以完成同一个作用。

var race = {
  protoss: 'askdjaslkfjas;lfkjas;flkj', // 神族
  terran: ';lkfalksjfl;askjfsfal;skfj', // 人族
  zerg: 'qwieqwoirqwoiruoiwqoisrqwroiu' // 虫族
}

也就是说, race.zerg 的值是多少并不重要,只要它的值跟 race.protossrace.terran 的值不一样就行,Symbol 的用途就是如此。

凡是数据类型属于 Symbol 类型,就都是独一无二的

所以可以用以下代码来声明,并且保证不会出现冲突的属性值:

var race = {
  protoss: Symbol(),
  terran: Symbol(),
  zerg: Symbol()
}
// 抑或填入描述
var race = {
  protoss: Symbol('protoss'),
  terran: Symbol('terran'),
  zerg: Symbol('zerg')
}

至此我觉得 Symbol 数据类型已经解释清楚了,如果还是不明白,请重新回到头部阅读一遍。


Symbol 拥有的一些特性

一、Symbol 作为属性名进行遍历时:

  • Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 返回。
  • Symbol 也不是私有属性。

可以通过 Object.getOwnPropertySymbols() 方法来获取指定对象的所有 Symbol 属性名(方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值)

const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

Object.getOwnPropertySymbols(obj);
// [Symbol(a), Symbol(b)]

当然也可以使用 ES6 的新 API Reflect.ownKeys() 来获取所有类型的键名,包括 常规键名Symbol 键名。

二、Symbol.for() 方法

Symbol.for()方法可以接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。
如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 
// true

Symbol.for("bar") === Symbol.for("bar")
// true

Symbol.for()Symbol() 都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

三、Symbol.keyFor() 方法

Symbol.keyFor() 方法返回一个已登记的 Symbol 类型值的 key

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

四、内置的 Symbol 属性

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

  1. Symbol.hasInstance

  2. Symbol.isConcatSpreadable

  3. Symbol.species

  4. Symbol.match

  5. Symbol.replace

  6. Symbol.search

  7. Symbol.split

  8. Symbol.iterator

  9. Symbol.toPrimitive

  10. Symbol.toStringTag

  11. Symbol.unscopables

这11个属性具体的说明看阮一峰老师的 ECMAScript 6入门 就可以了,里边有单独的小Demo可以阅读,我就不单独说明了,因为如果全部说明的话,内容就太多了,并且和书的内容高度重复了。

「每日一题」JS 中的 Symbol 是什么? | 方应杭
Symbol - ECMAScript 6入门 | 阮一峰
Symbol - JavaScript | MDN
Symbol.prototype.description - JavaScript | MDN
Reflect - ECMAScript 6入门 | 阮一峰
Reflect.ownKeys() - JavaScript | MDN