单例模式需通过属性描述符严格控制实例唯一性:冻结实例(Object.freeze)、封锁构造函数(defineProperty禁用new)、结合preventExtensions/seal限制对象修改,并用getter惰性返回唯一实例。

单例模式的核心是确保一个类只有一个实例,并提供全局访问点。在 JavaScript 中,单纯靠构造函数或工厂函数无法真正阻止用户重复创建对象——除非配合属性描述符(Property Descriptor)对关键属性进行严格控制,比如冻结构造函数、禁用 new 操作、封锁原型链、或锁定实例引用本身。
用 Object.freeze 防止实例被篡改
创建单例后,应立即冻结对象,防止新增、删除或修改自有属性:
- 调用 Object.freeze(instance) 可使对象不可扩展、不可配置、不可写
- 注意:freeze 只作用于对象自身属性,不递归冻结嵌套对象;如需深度冻结,需手动遍历或使用深冻工具
- 冻结后尝试赋值(
instance.x = 1)在严格模式下会抛错,非严格模式下静默失败
用 defineProperty 封锁构造函数与 new 调用
避免他人通过 new Singleton() 或直接调用构造函数生成新实例:
- 将构造函数设为不可构造:用 Object.defineProperty 设置
configurable: false和writable: false - 在构造函数内部加判断:若
this instanceof Constructor为真,说明被 new 调用,直接抛错或返回已有实例 - 更稳妥的做法是把构造函数设为私有(闭包内定义),只暴露静态 getInstance 方法
用 Object.preventExtensions + Object.seal 控制对象生命周期
在 freeze 之前,可按需组合使用其他保护手段:
立即学习“Java免费学习笔记(深入)”;
- Object.preventExtensions(obj):禁止添加新属性,但允许修改和删除现有属性
-
Object.seal(obj):preventExtensions + 所有现有属性设为
configurable: false(即不能删、不能改描述符) - seal 后仍可写已有属性(除非显式设
writable: false),freeze 则进一步锁定 writable
用 getter 拦截并统一返回唯一实例
不暴露原始构造函数,而是通过命名空间或模块导出一个只读的实例访问器:
- 利用 Object.defineProperty 在模块对象上定义一个不可写、不可配置的 getter 属性
- getter 内部惰性初始化单例,并缓存结果,后续调用始终返回同一引用
- 示例:
Object.defineProperty(ns, 'instance', { get() { return instance ?? (instance = new Singleton()); }, enumerable: true, configurable: false });
单例的对象保护不是一劳永逸的事,关键在于组合运用属性描述符控制可写性、可配置性和可扩展性,并从设计源头切断非法实例化路径。不复杂但容易忽略。


















