Skip to content

Symbol 基础

ES6 引入了第七种原始数据类型 Symbol,用于创建唯一的标识符。

为什么需要 Symbol#

在 JavaScript 中,对象属性名只能是字符串,这会导致命名冲突:

// 两个库都想给对象添加 id 属性
const obj = {}
library1.addId(obj) // obj.id = 'lib1-123'
library2.addId(obj) // obj.id = 'lib2-456'(覆盖了!)
// 使用 Symbol 可以避免冲突
const id1 = Symbol('id')
const id2 = Symbol('id')
obj[id1] = 'lib1-123'
obj[id2] = 'lib2-456'
// 两个属性互不影响

创建 Symbol#

基本创建#

const sym1 = Symbol()
const sym2 = Symbol()
console.log(sym1 === sym2) // false(每个 Symbol 都是唯一的)
// 带描述符
const sym3 = Symbol('description')
console.log(sym3.toString()) // 'Symbol(description)'
console.log(sym3.description) // 'description'

🔶 不能使用 new#

// new Symbol(); // TypeError: Symbol is not a constructor

描述符只是备注#

const sym1 = Symbol('id')
const sym2 = Symbol('id')
console.log(sym1 === sym2) // false(即使描述符相同)

作为属性键#

定义 Symbol 属性#

const ID = Symbol('id')
const NAME = Symbol('name')
// 方式1:对象字面量
const user = {
[ID]: 1,
[NAME]: '张三',
age: 25,
}
// 方式2:点号不行,必须用方括号
user[ID] // 1
// user.ID; // undefined(这是访问字符串键 'ID')
// 方式3:Object.defineProperty
Object.defineProperty(user, Symbol('email'), {
value: 'test@test.com',
})

Symbol 属性不可枚举#

const ID = Symbol('id')
const user = {
[ID]: 1,
name: '张三',
age: 25,
}
// for...in 不会遍历 Symbol 属性
for (const key in user) {
console.log(key) // 'name', 'age'(没有 Symbol)
}
// Object.keys 也不包含
Object.keys(user) // ['name', 'age']
// Object.getOwnPropertyNames 也不包含
Object.getOwnPropertyNames(user) // ['name', 'age']
// 需要使用专门的方法
Object.getOwnPropertySymbols(user) // [Symbol(id)]
// 或者 Reflect.ownKeys 获取所有键
Reflect.ownKeys(user) // ['name', 'age', Symbol(id)]

实际应用#

私有属性模拟#

const _count = Symbol('count')
const _increment = Symbol('increment')
class Counter {
constructor() {
this[_count] = 0
}
[_increment]() {
this[_count]++
}
tick() {
this[_increment]()
}
get value() {
return this[_count]
}
}
const counter = new Counter()
counter.tick()
counter.tick()
console.log(counter.value) // 2
console.log(counter[_count]) // 如果能访问到 Symbol,仍然可以读取
// 但外部代码通常无法获取这个 Symbol

防止属性覆盖#

// 框架定义的属性
const INTERNAL = Symbol('internal')
function createComponent(options) {
const component = {
...options,
[INTERNAL]: {
mounted: false,
listeners: [],
},
}
return component
}
// 用户不会意外覆盖内部属性
const comp = createComponent({
name: 'MyComponent',
internal: 'user data', // 不会与 INTERNAL 冲突
})

常量定义#

// 传统方式可能会冲突
const STATUS_PENDING = 'pending'
const STATUS_SUCCESS = 'success'
const STATUS_ERROR = 'error'
// 如果其他地方也定义了 'pending',比较时会相等
// 使用 Symbol 确保唯一
const STATUS = {
PENDING: Symbol('pending'),
SUCCESS: Symbol('success'),
ERROR: Symbol('error'),
}
function handleStatus(status) {
switch (status) {
case STATUS.PENDING:
return '处理中...'
case STATUS.SUCCESS:
return '成功'
case STATUS.ERROR:
return '错误'
default:
throw new Error('未知状态')
}
}

消除魔术字符串#

// 魔术字符串
function getArea(shape, options) {
switch (shape) {
case 'circle': // 魔术字符串
return Math.PI * options.radius ** 2
case 'rectangle':
return options.width * options.height
}
}
// 使用 Symbol
const SHAPES = {
CIRCLE: Symbol('circle'),
RECTANGLE: Symbol('rectangle'),
}
function getArea(shape, options) {
switch (shape) {
case SHAPES.CIRCLE:
return Math.PI * options.radius ** 2
case SHAPES.RECTANGLE:
return options.width * options.height
}
}
getArea(SHAPES.CIRCLE, { radius: 5 })

类型转换#

转字符串#

const sym = Symbol('foo')
String(sym) // 'Symbol(foo)'
sym.toString() // 'Symbol(foo)'
// 不能隐式转换
// 'symbol: ' + sym; // TypeError
// `symbol: ${sym}`; // TypeError
// 必须显式转换
;`symbol: ${String(sym)}` // 'symbol: Symbol(foo)'
;`symbol: ${sym.toString()}` // 'symbol: Symbol(foo)'

转布尔值#

const sym = Symbol()
Boolean(sym) // true
!sym // false
if (sym) {
console.log('Symbol 是真值')
}

🔶 不能转数字#

const sym = Symbol()
// Number(sym); // TypeError
// sym + 1; // TypeError

JSON 序列化#

Symbol 属性不会被 JSON.stringify 序列化:

const ID = Symbol('id')
const user = {
[ID]: 1,
name: '张三',
}
JSON.stringify(user) // '{"name":"张三"}'(没有 Symbol 属性)

常见问题#

🙋 Symbol 和字符串有什么区别?#

特性Symbol字符串
唯一性每个 Symbol 唯一相同字符串相等
创建Symbol()’…’ 或 new String()
类型symbolstring
可枚举性默认不可枚举默认可枚举
JSON不序列化正常序列化

🙋 什么时候用 Symbol?#

🙋 Symbol 是真正的私有属性吗?#

不是。Symbol 属性可以通过 Object.getOwnPropertySymbols()Reflect.ownKeys() 获取。它只是”隐藏”了属性,但没有真正的访问控制。

真正的私有属性应该使用 ES2022 的 # 语法:

class Counter {
#count = 0 // 真正的私有属性
increment() {
this.#count++
}
}

小结#

特性说明
唯一性每个 Symbol 都是唯一的
创建Symbol() 或 Symbol(‘description’)
属性键只能用方括号语法 obj[sym]
可枚举不被 for…in、Object.keys 等遍历
获取Object.getOwnPropertySymbols()
类型转换可转字符串和布尔值,不能转数字

Symbol 为 JavaScript 提供了创建唯一标识符的能力,在框架开发和元编程中非常有用。