Symbol 是 ES6 引入的第七种原始数据类型,用于创建独一无二的值。它解决了 JavaScript 中一些长期存在的问题,特别是属性名冲突和对象扩展方面的问题。

Symbol 的基本特性

1. 唯一性

每个 Symbol 都是独一无二的,即使描述相同:

const sym1 = Symbol('description')
const sym2 = Symbol('description')
 
console.log(sym1 === sym2) // false
console.log(typeof sym1)   // 'symbol'

2. 不可枚举性

Symbol 属性不会出现在常规的属性枚举中:

const sym = Symbol('hidden')
const obj = {
  name: 'Vue',
  [sym]: 'secret value'
}
 
console.log(Object.keys(obj))           // ['name']
console.log(Object.getOwnPropertyNames(obj)) // ['name']
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(hidden)]

主要用途和应用场景

1. 避免属性名冲突

在多人协作或使用第三方库时,Symbol 可以确保属性名的唯一性:

// 库 A
const LIBRARY_A_CONFIG = Symbol('config')
obj[LIBRARY_A_CONFIG] = { version: '1.0' }
 
// 库 B  
const LIBRARY_B_CONFIG = Symbol('config')
obj[LIBRARY_B_CONFIG] = { theme: 'dark' }
 
// 不会互相冲突
console.log(obj[LIBRARY_A_CONFIG]) // { version: '1.0' }
console.log(obj[LIBRARY_B_CONFIG]) // { theme: 'dark' }

2. 模拟私有属性和方法

虽然不是真正的私有,但 Symbol 可以创建”半私有”的属性:

const _counter = Symbol('counter')
const _increment = Symbol('increment')
 
class Counter {
  constructor() {
    this[_counter] = 0
  }
  
  [_increment]() {
    this[_counter]++
  }
  
  get value() {
    return this[_counter]
  }
  
  tick() {
    this[_increment]()
  }
}
 
const counter = new Counter()
counter.tick()
console.log(counter.value) // 1
 
// 外部无法直接访问
console.log(counter._counter) // undefined

3. 实现迭代器协议

使用 Symbol.iterator 让对象可迭代:

const fibonacci = {
  [Symbol.iterator]: function* () {
    let [prev, curr] = [0, 1]
    while (true) {
      yield curr
      ;[prev, curr] = [curr, prev + curr]
    }
  }
}
 
// 使用 for...of 迭代
const fib = fibonacci[Symbol.iterator]()
console.log(fib.next().value) // 1
console.log(fib.next().value) // 1  
console.log(fib.next().value) // 2
console.log(fib.next().value) // 3

4. 自定义对象转换行为

const obj = {
  value: 42,
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return this.value
    }
    if (hint === 'string') {
      return `Value: ${this.value}`
    }
    return this.value
  }
}
 
console.log(+obj)        // 42 (number)
console.log(`${obj}`)    // "Value: 42" (string)
console.log(obj + 10)    // 52 (default)

全局 Symbol 注册表

Symbol.for() 和 Symbol.keyFor()

// 创建或获取全局 Symbol
const globalSym1 = Symbol.for('app.config')
const globalSym2 = Symbol.for('app.config')
 
console.log(globalSym1 === globalSym2) // true
 
// 获取全局 Symbol 的 key
console.log(Symbol.keyFor(globalSym1)) // 'app.config'
 
// 普通 Symbol 没有 key
const localSym = Symbol('local')
console.log(Symbol.keyFor(localSym)) // undefined

内置 Well-known Symbols

JavaScript 提供了许多内置的 Symbol,用于自定义对象行为:

常用的内置 Symbols

// Symbol.iterator - 迭代器
const iterable = {
  *[Symbol.iterator]() {
    yield 1
    yield 2
    yield 3
  }
}
 
// Symbol.toStringTag - 自定义 toString 行为
class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClass'
  }
}
console.log(Object.prototype.toString.call(new MyClass())) 
// [object MyClass]
 
// Symbol.hasInstance - 自定义 instanceof 行为
class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance)
  }
}
console.log([] instanceof MyArray) // true
 
// Symbol.species - 指定创建衍生对象时使用的构造函数
class MyArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
}

实际应用示例

1. 创建枚举值

const Colors = {
  RED: Symbol('red'),
  GREEN: Symbol('green'),  
  BLUE: Symbol('blue')
}
 
function getColorName(color) {
  switch (color) {
    case Colors.RED:
      return 'Red'
    case Colors.GREEN:
      return 'Green'
    case Colors.BLUE:
      return 'Blue'
    default:
      return 'Unknown'
  }
}

2. 元数据存储

const metadata = Symbol('metadata')
 
class Component {
  constructor(name) {
    this.name = name
    this[metadata] = {
      created: new Date(),
      version: '1.0.0'
    }
  }
  
  getMetadata() {
    return this[metadata]
  }
}

3. 插件系统

const PLUGIN_HOOKS = Symbol('pluginHooks')
 
class PluginSystem {
  constructor() {
    this[PLUGIN_HOOKS] = new Map()
  }
  
  registerHook(name, callback) {
    if (!this[PLUGIN_HOOKS].has(name)) {
      this[PLUGIN_HOOKS].set(name, [])
    }
    this[PLUGIN_HOOKS].get(name).push(callback)
  }
  
  executeHooks(name, ...args) {
    const hooks = this[PLUGIN_HOOKS].get(name) || []
    return hooks.map(hook => hook(...args))
  }
}

注意事项

  1. 不能使用 new:Symbol 不是构造函数

    const sym = new Symbol() // TypeError
  2. 类型转换限制:Symbol 不能隐式转换为字符串或数字

    const sym = Symbol('test')
    console.log(sym + '') // TypeError
    console.log(String(sym)) // "Symbol(test)" - 显式转换可以
  3. JSON 序列化:Symbol 属性不会被 JSON.stringify 序列化

    const sym = Symbol('key')
    const obj = { [sym]: 'value', normal: 'data' }
    console.log(JSON.stringify(obj)) // {"normal":"data"}

总结

Symbol 的主要价值在于:

  • 唯一性:解决命名冲突问题
  • 隐藏性:创建”半私有”属性
  • 扩展性:通过 well-known symbols 扩展对象行为
  • 元编程:为高级 JavaScript 编程提供工具

Symbol 是现代 JavaScript 开发中重要的工具,特别是在库和框架开发中发挥着重要作用。