Symbol实现单例模式的核心是利用其唯一性与不可枚举性:通过Symbol作静态缓存键拦截重复构造,或结合WeakMap实现类级隔离与自动回收,再用defineProperty隐藏Symbol字段确保私有性,禁用Symbol.for以防跨模块污染。

用 Symbol 实现单例模式,核心不是“加锁”,而是利用 Symbol 的唯一性与不可枚举性,在类或模块层面控制实例的创建与复用,从而确保全局只有一个对象引用。
用 Symbol 作静态缓存键,拦截重复构造
在类外部声明一个 Symbol,作为该类实例的唯一存储标识。构造函数中检查该 Symbol 对应的静态属性是否存在;若已存在,直接返回,不再执行初始化逻辑:
- Symbol 键不会出现在
for...in、Object.keys()或JSON.stringify()中,外部无法通过常规方式发现或覆盖 - 即使多个类使用相同描述字符串(如
Symbol('instance')),生成的值仍互不相等,天然避免命名冲突 - 示例写法:
const INSTANCE = Symbol('loggerInstance');<br>class Logger {<br> constructor() {<br> if (Logger[INSTANCE]) return Logger[INSTANCE];<br> Logger[INSTANCE] = this;<br> this.count = 0;<br> }<br>}
用 WeakMap + Symbol 实现更健壮的类级隔离
直接挂载在类上的静态属性(如 Logger[INSTANCE])可被手动赋值或删除,风险较高。改用 WeakMap 存储,以类本身为键、实例为值,既保证每个类独享单例,又支持垃圾回收:
- WeakMap 的键必须是对象,天然适配构造函数(即类本身),且不阻止其被回收
- Symbol 在此处仅作逻辑标识(如
Symbol('singletonCache')),实际起作用的是 WeakMap 的set/get行为 - 示例:
const KEY = Symbol('dbKey');<br>const cache = new WeakMap();<br>class Database {<br> constructor() {<br> if (cache.has(this.constructor)) {<br> return cache.get(this.constructor);<br> }<br> cache.set(this.constructor, this);<br> }<br>}
配合 defineProperty 隐藏关键状态字段
单例对象自身的属性(如 this._data)仍可能被外部意外覆盖。可用 Symbol 命名内部字段,并通过 Object.defineProperty 设置 writable: false 和 enumerable: false:
- Symbol 字段默认不可枚举,再设为只读后,外部无法修改、遍历、序列化
- 注意:Symbol 属性必须用方括号访问(
obj[sym]),点语法无效 - 示例:
const _id = Symbol('_id');<br>class Config {<br> constructor(id) {<br> Object.defineProperty(this, _id, {<br> value: id,<br> writable: false,<br> enumerable: false<br> });<br> }<br>}
慎用 Symbol.for —— 它会破坏单例边界
Symbol.for('key') 会在全局注册表中查找或创建 Symbol,所有同名调用返回同一个值。这看似方便,但会导致不同模块间无意共享单例,违背“类级唯一”的设计意图:
- 例如两个独立的
Logger类都用Symbol.for('logger'),它们将共用一个实例,引发状态污染 - 推荐始终使用无参数的
Symbol()或带唯一描述的Symbol('MyClassInstance') - 真正需要跨模块共享时,应显式导出实例,而非依赖全局 Symbol 注册表










