Symbol 详解

ES6 之前,js 中的基本数据类型一共有 String、Number、Boolean、Null 和 Undefined 5种, ES6 新增了一种基本数据类型 —— Symbol。

为什么需要 Symbol

那么为什么要新增 Symbol 这种数据类型呢?它能解决什么问题呢? 我们知道,在 ES6 之前,对象的属性名都是字符串,容易造成重名的问题,比如有个从别人那获得的对象 obj ,原本有个属性 name,在使用时没注意,或者不知道该对象已经有了 name 属性,而给 obj 重复定义了 name,原本的 name 的值就会被覆盖。ES6 开始,对象的属性名还可以使用由 Symbol() 函数返回得到的 Symbol 类型的值,它们是独一无二的,从而解决了属性名命名冲突问题。

如何生成 symbol

前面也提到了,生成 Symbol 类型数据的方法就是使用 Symbol 函数:

// 例 1
const sym1 = Symbol()
const sym2 = Symbol()
console.log(sym1 === sym2) // false

例 1 中的 sym1sym2 都是 Symbol 类型的数据,且不相等。请注意,虽然 Symbol 的首字母是大写的 S,看起来像是个构造函数,但它是不能通过 new 调用的。 如果想要得到两个相等的 symbol 值,则需要使用 Symbol.for() 方法,并传入一个 key 作为参数:

// 例 1.1.1
const sym1 = Symbol.for('123456')
const sym2 = Symbol.for('123456')
console.log(sym1 === sym2) // true

实际上例 1.1.1 第 3 行并没有新创建一个 symbol,而是使用给定的 key(123456)搜索现有的 symbol,找到了第 2 行生成的 symbol 并返回给了 sym2,所以 sym1自然和 sym2 是相等的。如果根据 key 没找到,就会使用 key 在全局 symbol 注册表中创建一个新的 symbol。 想要得到例 1.1.1 中 Symbol.for 设定的 key,则可以通过将 Symbol 实例传给 Symbol.keyFor() 得到:

// 例 1.1.2
console.log(Symbol.keyFor(sym1)) // 123456

描述(description )

从 ES2019(ES10)开始,我们还可以给 Symbol()传入字符串类型的参数作为描述。

// 例 1.2
const sym1 = Symbol('我是 sym1')
const sym2 = Symbol('我是 sym2')
console.log(sym1) // Symbol(我是 sym1)
console.log(sym2) // Symbol(我是 sym2)

描述仅仅是为了方便调试,即使在例 1.2 中,第 2、3 行传入一样的描述,得到的 sym1sym2 也不会相等。描述可以通过 Symbol 的实例属性 description 获得:

// 例 1.3
console.log(sym1.description) // 我是 sym1

如何使用 symbol

想要将 Symbol 类型的数据作为对象的 key,可以直接使用 ES6 对于对象字面量进行的增强写法 —— 计算属性名([]):

// 例 2.1
const sym1 = Symbol('name')
const sym2 = Symbol('age')
const obj = {
  [sym1]: 'Jay',
  [sym2]: 40
}

新增的属性如果想使用 symbol 作为 key,也是将得到的 Symbol 实例放入中括号即可:

// 例 2.2
const sym3 = Symbol('address')
obj[sym3] = 'Taipei'

如果通过 Object.defineProperty() 等函数定义与控制对象的属性也想为 symbol,可以:

// 例 2.3
const sym4 = Symbol('gender')
Object.defineProperty(obj, sym4, {
  configurable: true,
  enumerable: true,
  writable: true,
  value: '男'
})

要获取 symbol 作为 key 的属性值,也是用中括号:

// 例 2.4
console.log(obj[sym1], obj[sym2], obj[sym3], obj[sym4]) // Jay 40 Taipei 男

如何获取对象中的 symbol 属性

获取对象属性的方法中,for inObject.keys()(虽然 MDN 文档中没有明确指出除 symbol 以外)和Object.getOwnPropertyNames() 都无法获取 Symbol 值作为名称的属性。想要获取一个对象的 symbol 属性,可以用下面 2 个方法:

// 例 3.1
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(name), Symbol(age), Symbol(address), Symbol(gender) ]
console.log(Reflect.ownKeys(obj)) // [ Symbol(name), Symbol(age), Symbol(address), Symbol(gender) ]

两个方法得到的都是由属性组成的数组,区别是 Object.getOwnPropertySymbols是专门获取对象的 symbol 属性的,而 Reflect.ownKeys 获取的是对象自身的所有属性。

内置 symbol 值

js 中内置了一些 symbol 值,指向语言内部使用的方法,比如我们查看数组对象的时候,可以在它的隐式原型上看到一个描述为 Symbol.iterator 的 symbol 属性:

image.png

该属性指向对象的默认遍历器方法。当使用 for of 去遍历某一个数据结构的时候,会首先去找这个对象有没有属性[Symbol.iterator],它的值是一个函数,返回该对象默认迭代器的方法,有就说明其为可迭代对象,可以用 for of 遍历。