分享我的单例写法:
function instDefine<T>(target: object, key: string, creator: () => T) {
function getInst() {
const value = creator();
if (!value) {
return value;
}
Reflect.defineProperty(target, key, {
value,
writable: false,
configurable: true,
enumerable: true,
});
director.on(Director.EVENT_RESET, defineGetter);
return value;
}
function defineGetter() {
Reflect.defineProperty(target, key, {
get: getInst,
enumerable: true,
configurable: true,
});
}
defineGetter();
return getInst;
}
export const CCSingleton = <T>() => ({
get base () {
return class CCSingleton {
protected constructor() { }
static get inst() {
const creator = () => new this() as T;
const getInst = instDefine(this, 'inst', creator);
return getInst();
}
}
},
from<B extends Constructor<object>>(Base: B, ...args: any[]) {
return class CCSingleton extends Base {
protected constructor(...args: any[]) { super(...args); }
static get inst() {
const creator = () => new this(...args) as T;
const getInst = instDefine(this, 'inst', creator);
return getInst();
}
}
},
})
用法:
export class XXXMgr extends CCSingleton<XXXMgr>().base {
hello() {
console.log("hello, world!");
}
}
XXXMgr.inst.hello();
export class EventCenter extents CCSingleton<EventCenter>().from(EventTarget) { }
EventCenter.inst.emit("test");
原理:
-
基类中定义static inst getter来创建单例实例
-
inst getter被调用时,重新定义构造函数上的inst属性,用创建好的value替换掉原来的inst getter
-
cocos重新启动时,把inst属性复原为inst getter
-
这种在定义类时把类名作为泛型参数传给基类的做法叫做CRTP模式,这个写法来自C++,目的是让基类能够使用子类(例如我代码中的as T)。
优势:
-
强类型,良好的编辑器提示支持
-
延迟创建,只有在第一次访问inst属性的时候,实例才会被创建
-
性能,实例创建后直接覆盖原有getter,后续访问不再有函数调用开销
-
灵活,可以通过from方法来指定被继承的基类
-
结合cocos生命周期,游戏重启时自动清除现有实例,避免内存数据遗留