用getter实现单例的核心是延迟初始化:首次访问属性时创建实例并缓存,后续直接返回;通过object.defineproperty定义只读、不可配置的getter,结合闭包变量确保唯一性与按需加载。

用存取描述符 getter 实现单例,核心是把实例创建逻辑藏在 getter 里,首次访问时才初始化,之后始终返回同一个对象——真正做到了延迟、按需、只初始化一次。
用 Object.defineProperty 定义只读的单例属性
在模块顶层或类静态属性上,通过 Object.defineProperty 设置一个不可写、不可枚举、不可配置的 getter 属性。这样外部只能读取,无法覆盖,也看不到原始定义过程。
- 把实际的实例创建逻辑(比如 new SomeService() 或复杂初始化函数)写在 getter 的函数体里
- 用闭包变量缓存实例,确保后续调用直接返回已创建的对象
- 设置
writable: false和configurable: false防止被意外修改
避免重复执行初始化逻辑
getter 内部必须做“是否已存在实例”的判断。常见做法是用一个私有变量(如 let instance = null)在闭包中保存状态。一旦赋值,后续调用就跳过构造逻辑。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 不要在 getter 外部提前声明实例变量并赋值,否则就失去了“延迟”意义
- 如果初始化逻辑可能抛错,建议在 getter 内加 try/catch,并考虑是否缓存错误(避免反复失败重试)
- 不建议在 getter 中做异步初始化(如 await fetch),因为 getter 应同步返回;如需异步,应改用函数调用 + 缓存 Promise
适用于工具类、配置服务、第三方 SDK 封装等场景
比如封装 localStorage 操作、全局事件总线、Axios 实例、或某地图 SDK 的初始化入口。这些对象通常整个应用只需一份,且初始化开销较大(如加载脚本、建立连接),适合延迟到真正使用时才触发。
- 例如:
const apiClient = { get client() { if (!instance) instance = createApiClient(); return instance; } } - 配合 ES 模块的顶层作用域,天然支持跨文件共享同一实例,无需额外依赖注入
- 比 class static 实现更轻量,也不依赖框架生命周期,纯 JS 即可落地
注意与模块级单例的区别
ES 模块本身具有单例语义(多次 import 同一模块,导出对象是同一个引用),但模块顶层代码会在导入时立即执行。而 getter 方式能进一步推迟到属性首次被读取那一刻,真正实现“按需”。
- 模块顶层 const 声明 + new 实例 → 导入即初始化
- 模块顶层 getter 描述符 → 导入时不执行,首次
obj.prop才触发 - 两者可结合:模块导出一个带 getter 的对象,既利用模块缓存,又控制初始化时机
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










