symbol 作为对象属性键可避免命名冲突且不可枚举,适合定义私有属性;推荐用 symbol() 创建唯一键并复用,配合 weakmap 实现真正隔离的私有状态;symbol.for() 需谨慎使用以防全局冲突。

用 Symbol 作为对象属性键名,能天然避免命名冲突,因为每个 Symbol 值都是唯一且不可枚举的——第三方库即使遍历对象,也拿不到你用 Symbol 定义的属性,更不会意外覆盖。
创建私有、防覆盖的 Symbol 键
用 Symbol() 创建一个全局唯一的键,赋值给变量后复用:
- 不要每次写
Symbol('id')—— 这会生成新值,无法访问原属性; - 推荐定义一次并导出(如模块内):
export const internalId = Symbol('internalId'); - 然后:
obj[internalId] = 123;—— 这个属性不会出现在for...in、Object.keys()或JSON.stringify()中。
配合 WeakMap 实现真正隔离的私有状态
如果想让属性完全不暴露在目标对象上(连 Object.getOwnPropertySymbols() 都查不到),就用 WeakMap:
- 创建单例
const privateStore = new WeakMap(); - 存值:
privateStore.set(obj, { userId: 456, token: 'abc' }); - 取值:
privateStore.get(obj)?.userId; - 优势:对象销毁时自动清理,不造成内存泄漏;第三方库根本接触不到这个映射关系。
谨慎使用 Symbol.for() —— 全局注册需权衡
Symbol.for('myKey') 会在全局符号注册表中查找/创建,同名返回同一个 Symbol。这适合跨模块共享键,但也有风险:
- 若第三方库也调用
Symbol.for('cache'),就会共享甚至覆盖你的属性; - 除非明确需要协作(如自定义迭代器
[Symbol.iterator]),否则优先用无参Symbol(); - 若必须用
Symbol.for(),建议加前缀降低冲突概率,例如Symbol.for('mylib-cache')。
实际场景中的典型用法
比如封装一个带内部计数器的工具类:
- 用
const counter = Symbol('counter');存实例私有计数; - 方法里通过
this[counter]++操作,外部无法直接读写; - 即使用户执行
Object.assign(target, instance),counter属性也不会被拷贝过去(默认忽略 Symbol 键); - 若需浅拷贝 Symbol 属性,得显式处理:
Object.assign(target, { [counter]: instance[counter] })。
不复杂但容易忽略:Symbol 键不是“绝对私有”,它只是隐式、不可枚举、不参与常规遍历——真正的隔离还得靠设计约束和 WeakMap 配合。










