Angular单例实例设为Undefined后变量引用持久化问题
问题代码
单例类
export class SingletonClass { public static instance: SingletonClass | undefined; public name: string | undefined | null; static getInstance() { if (SingletonClass.instance == null || SingletonClass.instance == undefined) SingletonClass.instance = new SingletonClass(); return SingletonClass.instance; } static destroy() { SingletonClass.instance = undefined; } }
组件代码
export class TestComponent implements OnInit { SingletonObject1: SingletonClass | undefined | null = SingletonClass.getInstance(); SingletonObject2: SingletonClass | undefined | null; ngOnInit(): void { this.SingletonObject1!.name = "Name1"; SingletonClass.destroy(); this.SingletonObject2 = SingletonClass.getInstance(); this.SingletonObject2.name = "Name2"; } }
问题描述
预期SingletonObject1和SingletonObject2指向同一单例对象,最终两者的name属性都应为"Name2",但实际结果是SingletonObject1.name为"Name1",SingletonObject2.name为"Name2"。移除SingletonClass.destroy()则符合预期,说明调用该方法后SingletonObject1仍保留旧对象引用。想知道:
- 为何设实例为
undefined会生成两个实例? - 如何修改
destroy方法以彻底移除单例对象并让两个变量保持同一引用?
为什么会生成两个实例?
JS/TS里对象是引用类型。你调用SingletonClass.destroy()把静态的instance设为undefined,只是让类自己不再持有旧实例的引用,但SingletonObject1这个变量还攥着旧实例的内存地址没放。
之后调用getInstance(),因为静态instance是空的,所以会新建一个完全不同的实例,SingletonObject2指向这个新对象。现在SingletonObject1和SingletonObject2各指各的,改一个当然不会影响另一个。
怎么改才能实现需求?
如果想让销毁后重新获取的实例,还能让所有旧引用都指向它,不能直接换掉实例,得复用同一个对象,只重置它的内部属性。改法如下:
调整后的单例类
export class SingletonClass { public static instance: SingletonClass | undefined; public name: string | undefined | null; // 把构造函数私有化,防止外部直接new private constructor() { this.resetState(); } static getInstance() { if (!SingletonClass.instance) { SingletonClass.instance = new SingletonClass(); } return SingletonClass.instance; } static destroy() { // 不销毁实例,只重置它的内部状态 SingletonClass.instance?.resetState(); } // 专门的重置方法,统一管理需要恢复默认的属性 private resetState() { this.name = null; } }
这样改后的效果
调用destroy()后,SingletonObject1拿着的还是原来那个实例,只是它的属性被重置了。之后getInstance()返回的还是同一个实例,SingletonObject2也指向它。这时候改SingletonObject2.name = "Name2",SingletonObject1.name也会变成"Name2",完全符合你的预期。
要是你确实需要彻底销毁实例(比如要释放大内存资源),同时让旧引用自动指向新实例,这在JS/TS里做不到——因为旧变量的引用没法被强制修改。这种情况只能换思路:
- 别让外部变量缓存单例实例,每次操作都通过
SingletonClass.getInstance()去拿 - 或者给单例加个方法,外部每次通过这个方法获取实例,内部维护唯一实例,但本质还是要每次调用方法,不能存引用
内容的提问来源于stack exchange,提问作者Nalaka526

