函数实现单例模式
单例模式
一般在前端实现单例模式,大多数都会使用类去实现,因为类的实现,看起来比较简单,下面是一个简单的例子。
class Foo {
static instance;
static init() {
if (!this.instance) this.instance = new Foo();
return this.instance;
}
constructor() {}
}
// 将单例实例化 并暴露出去
export default Foo.init()
如此,我们就实现了简单的单例模式,并且在其他文件引入的时候已经是实例化过一次的了,或者交由用户者自行调用 init 也是可以的
函数实现
而在函数的实现上,其实本身类就是函数的某种抽象,如果去掉这个 new 的话,单纯用函数又是怎么做的呢?
let ipcMainInstance;
export default () => {
const init = () => {
return {
name: "phy",
hobby: "play games"
};
};
return () => {
if (!ipcMainInstance) {
ipcMainInstance = init();
}
return ipcMainInstance;
};
};
使用
const ipcInit = createIpc();
ipcInit();
因为我们使用的是二阶函数进行 init,所以写法上是二次调用才是 init,每个人的设计写法不一样。
然而这种写法上,每次都要写一个 init 方法进行单例实例化的包裹,这明显是一个重复工作,我们是否可以将 init 方法独立成一个函子,让他帮我们自动将我们传进去的函数进行处理,返回来的就是一个单例模式的函数呢?
抽象单例模式函子
// 非void返回值
type NonVoidReturn<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R extends void
? never
: T
: any;
/**
* 创建单例模式的函子
* @param {function} fn
* @returns {any} fn调用的返回值 必须得有return 可推断
*/
const createSgp = <T extends (...args: any) => any>(fn: NonVoidReturn<T>) => {
let _instance: undefined | ReturnType<T>;
return () => {
if (!_instance) {
_instance = fn();
}
return _instance;
};
};
export default createSgp;
使用上
import createSgp from "./createSgp";
const useAuto = () => {
let count = 0;
const setCount = (num: number) => {
count = num;
};
const getCount = () => count
return {
getCount,
setCount
};
};
// 将其处理成单例模式 并且暴露出去
export default createSgp(useAuto);
如此我们就完成了单例模式的包裹处理,并且是一个单例模式的函数。
对于hooks使用单例模式函数的问题
其实上面的操作看起来很酷,实际上很少会用到,因为你得考虑到,我用单例模式的意义是什么,如果这个函数只需要调用一次,那么就有必要用单例模式,但是hooks一般用到的时候,都属于操作性逻辑,尽量不应在hooks里面去做hooks初始化时有函数自执行调用,这个调用应该交由用户去做,我是这么理解hooks的,而这也就导致,hooks不应该用单例了,而且hooks用单例会有bug,请看下面的代码:
let count = 0;
const useCount = {
count,
add(num){
count += num
}
}
这里我就一次简化useCount的return出来的东西,那么我们思考下,如果说,这个add在外部调用了,那么这个count会变吗?答案是不会,为什么呢?
因为当前add操作的count,是外部的count,并不是return对象的count,这句话可能很绕,但是仔细思考,一开始useCount(),他return的count是长什么样,此时,他其实就是数字0,那么,add改的count真的是这个return对象的count吗?相信说到这里,你就懂为什么了。
那我如果真的要联动到这个count,怎么做呢?
const useCount = {
count: 0,
add(num){
this.count += num
}
}
答案是,用到this,此时这个add操作的count就是此时return 对象的count了,而这也跟类一个原理了,因为类更改的成员属性,都是实例对象本身的,而不是外部的,所以,他能更新上。这个问题,也是后面我发现的,所以以此记录一下。
来源:juejin.cn/post/7232499216529834039