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) // undefined3. 实现迭代器协议
使用 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) // 34. 自定义对象转换行为
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))
}
}注意事项
-
不能使用 new:Symbol 不是构造函数
const sym = new Symbol() // TypeError -
类型转换限制:Symbol 不能隐式转换为字符串或数字
const sym = Symbol('test') console.log(sym + '') // TypeError console.log(String(sym)) // "Symbol(test)" - 显式转换可以 -
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 开发中重要的工具,特别是在库和框架开发中发挥着重要作用。