前言

ES6 更新了第 7 种根底数据类型 symbol,又称符号类型,用于作为全局惟一的示意符。并且以 Symbol 动态属性的形式向前端开发者裸露了能够批改 JS 外部语言行为的办法,用于更深层次操作对象。

申明形式

能够通过调用全局的 Symbol 函数来失去一个符号类型的变量,并且能够传递一个字符串类型的参数,用于示意对此符号的形容,能够通过 description 属性拜访,然而两个形容雷同的符号,并不相等。

const sy = Symbol('符号的形容')console.log(sy) // Symbol(符号的形容)console.log(sy.description) // '符号的形容'const sy2 = Symbol('符号的形容')console.log(sy2 === sy) // falseconsole.log(sy2.description === sy.description) // true

符号形容的意义只在于不便开发者调试,是没方法通过形容获取它对应的符号的。

Symbol作为构造函数来说并不残缺,所以它不反对语法:new Symbol()

全局符号注册表

Symbol 有两个静态方法,用以保护一个全局符号注册表(能够视为一个 Map),实现了 symbol 与 description 间互相转换。

  • Symbol.for 同样能够创立一个符号,也承受一个字符串作为符号的形容。并将此符号退出全局注册表,如果下次通过此办法创立雷同形容的符号,会间接返回已创立的符号。
  • Symbol.keyFor 检测一个符号是否在全局注册表中注册过,如果有,返回它的形容。

说实话,在理论开发中基本用不到这两个办法,不会存在符号与形容互相转换的需要,但为了内容的完整性,还是简略介绍了一下。

Symbol 的动态属性

Symbol 有许多动态属性,开发者能够通过配置它们扭转 JS 的外部语言行为。在这里介绍其中较为罕用的三个。

  • Symbol.iterator,用于为对象定义迭代器,其值应是一个生成器函数。在下文中的扩大遍历章节,会应用此属性极大扩大 for of 的性能。
  • Symbol.toStringTag,用于批改对象的字符串标签,其值应是一个函数,函数的返回值为字符串,个别用于区分类的实例。
  • Symbol.toPrimitive,用于为对象定义转换为原始值时的行为,承受一个参数示意转换偏向。详情可看 援用数据类型的转换规则

Symbol 还有许多静态方法,在平时开发中简直用不到,这里不做介绍。

应用场景

Symbol 个别用于以下场景

定义类标签

能够在为类配置 Symbol.toStringTag 属性,很容易失去对象所属的类标签,应用 switch 进行匹配,比应用 instanceof 一次次比拟尝试更不便。

class Pattern {  get [Symbol.toStringTag]() {    return 'Pattern'  }}class Envelope {  get [Symbol.toStringTag]() {    return 'Envelope'  }}const pattern = new Pattern()const envelope = new Envelope()console.log(Object.prototype.toString.call(pattern)) // '[object Pattern]'console.log(Object.prototype.toString.call(envelope)) // '[object Envelope]'

当然,你也能够不依赖 Object.prototype.toString 办法,间接在类中通过其余属性定义它的类型。

class Pattern {  get classType() {    return 'Pattern'  }}class Envelope {  get classType() {    return 'Envelope'  }}const pattern = new Pattern()const envelope = new Envelope()console.log(pattern['classType']) // 'Pattern'console.log(envelope['classType']) // 'Envelope'

打消魔术字符串

魔术字符串指的是在代码之中屡次呈现、与代码造成强耦合的某一个具体的字符串。格调良好的代码中应该尽量打消魔术字符串。

以下实例中,classTypeEnvelopePattern 就是魔术字符串,它屡次呈现,与代码造成“强耦合”,不利于未来的批改和保护。

class Pattern {  get classType() {    return 'Pattern'  }}class Envelope {  get classType() {    return 'Envelope'  }}function drawItem(item) {  const type = item['classType']  switch (type) {    case 'Pattern':      break    case 'Envelope':      break  }}function deleteItem(item) {  const type = item['classType']  switch (type) {    case 'Pattern':      break    case 'Envelope':      break  }}

罕用的打消魔术字符串的办法就是把它写成一个变量。

咱们并不关怀这个变量的值是什么,只须要它全局惟一,这里应用 symbol 类型再适合不过。

const CLASS_TYPE = Symbol('classType')const PATTERN = Symbol('Pattern')const ENVELOPE = Symbol('Envelope')class Pattern {  get [CLASS_TYPE]() {    return PATTERN  }}class Envelope {  get [CLASS_TYPE]() {    return ENVELOPE  }}function drawItem(item) {  const type = item[CLASS_TYPE]  switch (type) {    case PATTERN:      break    case ENVELOPE:      break  }}function deleteItem(item) {  const type = item[CLASS_TYPE]  switch (type) {    case PATTERN:      break    case ENVELOPE:      break  }}

当我的项目大起来后,想要批改一个魔法字符串是真的苦楚,你须要在所有文件中搜寻搜寻这个字符串,一一地批改。

如果你是通过变量来应用的话,尽管在应用中须要先导入这个变量,但在你须要批改的时候,只须要通过编辑器重命名符号的性能,便可轻松实现全局批改。

作者目前正在学习 Vue3,在其源码中,存在着大量 const XXX = Symbol('xxx') 的语句。

定义公有属性

symbol 能够用来定义真正意义上的公有属性。

在上面这个例子中,只有 Pattern.js 不导出符号变量,从内部无论如何也拜访不到实例的公有属性。

// 类的定义文件 Pattern.jsconst PRIVATE_VALUE = Symbol('privateValue')export class Pattern {  get [PRIVATE_VALUE]() {    return '公有属性,你们拜访不到'  }  fun() {    console.log(this[PRIVATE_VALUE])  }}// 在其余文件import { Pattern } from 'Pattern.js'const pattern = new Pattern()console.log(pattern) // Pattern {}console.log(Object.getOwnPropertySymbols(pattern)) // []console.log(Object.keys(pattern)) // []pattern.fun() // '公有属性,你们拜访不到'

扩大遍历

ES6 新推出的 for of 遍历十分不便,但就是不能间接遍历对象,须要先调用 Object.keys 办法获取属性列表,能力遍历。

咱们能够通过 Symbol.iterator 配合生成器,将 Object.keys 操纵提前进行,不便咱们遍历对象。

Object.prototype[Symbol.iterator] = function* () {  let keys = Object.keys(this)  for (let i = 0; i < keys.length; i++) {    // yield [keys[i], this[keys[i]]]    yield keys[i]  }}const obj = { a: 1, b: 2, c: 3 }for (const key of obj) {  console.log(key) // 'a' 'b' 'c'}// for (const [key, value] of obj) {//   console.log(key, value)// }console.log([...obj]) // ['a', 'b', 'c']console.log({ ...obj }) // {a: 1, b: 2, c: 3}

如果你想更简便一点,能够应用下面被正文掉的代码,一次性获取对象的键与值。

咱们还能够扩大数字,代替手写传统的 for 循环。

Number.prototype[Symbol.iterator] = function* () {  for (let i = 0; i < this.valueOf(); i++) {    yield i  }}const arr = [...5]console.log(arr) // [0, 1, 2, 3, 4]for (const i of arr.length) {  console.log(arr[i]) // 0 1 2 3 4}

禁止对象的类型转换

当你的代码中呈现对象向根底数据类型转换时,这往往都是一个谬误,而且也不利于代码的浏览与保护。

能够通过配置 Symbol.toPrimitive 属性让代码间接报错。

const obj = {}console.log(obj == '[object Object]') // trueObject.prototype[Symbol.toPrimitive] = () => {  throw Error('不容许对象向根底数据类型转换')}console.log(obj == '[object Object]') // Error: 不容许对象向根底数据类型转换

兼容性

除了 IE 浏览器这个前端大毒瘤,其余支流浏览器都曾经实现了 symbol 的全副性能。

如果你的我的项目不须要兼容 IE,大胆的应用 symbol 不便你的开发吧。

结语

如果文中有谬误或不谨严的中央,请务必给予斧正,非常感激。

内容整顿不易,如果喜爱或者有所启发,心愿能点赞关注,激励一下作者。