前言
单例是什么?-----就是保证只有一个类,一个实例。
什么情况下需要单例模式呢?
假如你要做一个翻译软件,我们这里采用去问大模型的方式。我们给大模型一个 hello,然后这个大模型 model 就会返回给你翻译结果。
当你再问一次的时候,你会希望 model = new LLM() 在执行一次吗?加载一个大型语言模型可能需要占用数 GB 的内存,肯定是不想要的,所以这里就需要单例模式。让这个代码永远只有一个实例对象。
如何让一个类只有一个实例对象?
静态方法
我们在这创建一个类 SingleDog,
class SingleDog {
show() {
console.log('这是一个单例模式');
}
}
const s1 = new SingleDog()
const s2 = new SingleDog()
console.log(s1 === s2) // falsse
注意:这里的s1和s2不相等 --- 引用地址不一样
我们想要让这个类只有一个实例对象,也就是我们希望这里 new 出来的实例对象是相等的。这是什么意思呢?
我们希望,new 多次,希望得到的一直是第一次的实例对象。
实现单例模式
这里就需要构造函数有一个能力了,构造函数需要知道自己有没有被调用过。
我这里提出一个方法,来看看这样可不可行:
//静态方法:只有构造函数能调用
class SingleDog {
constructor() {
this.count = 0 // 记录调用次数
this.obj = null
}
show() {
console.log('这是一个单例模式');
}
static getInstance() {
console.log(this.count); // 调试
if (this.count > 0) {
return this.obj // 第二次调用 retuen obj
}
this.count++
this.obj = new SingleDog() // 把实例对象放到 obj
return this.obj
}
const s1 = SingleDog.getInstance()
const s2 = SingleDog.getInstance()
console.log(s1 === s2)
结果是这样子的:false
为什么呢?
静态方法是直接在类上调用的,this 在这里并不指向实例,而是指向类本身,而 count 和 obj 是实例属性,而不是类的静态属性,所以类本身没有 count 和 obj 这两个实例属性。
那我们这里的 static getInstance() 可以改变一下:
class SingleDog {
show() {
console.log('这是一个单例模式');
}
static getInstance() {
if (!SingleDog.instance) { // 在这个方法挂一个属性
SingleDog.instance = new SingleDog
}
return SingleDog.instance
}
}
const s1 = SingleDog.getInstance()
const s2 = SingleDog.getInstance()
console.log(s1 === s2)
或者说也可以沿用上一个思想:
class SingleDog {
// 记录调用次数
static count = 0;
// 存储实例对象
static obj = null;
show() {
console.log('这是一个单例模式');
}
static getInstance() {
if (SingleDog.count > 0) {
return SingleDog.obj;
}
SingleDog.count++;
SingleDog.obj = new SingleDog();
return SingleDog.obj;
}
}
const s1 = SingleDog.getInstance();
const s2 = SingleDog.getInstance();
console.log(s1 === s2);
闭包
//闭包
class SingleDog {
show() {
console.log('这是一个单例模式');
}
}
SingleDog.getInstance = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new SingleDog();
}
return instance;
}
})(); // 自执行函数
const s1 = SingleDog.getInstance(); //调用的是 getInstance 里面的函数体
const s2 = SingleDog.getInstance();
console.log(s1 === s2);
getInstance 调用完,销毁掉会留下 instance 闭包,因为下面的函数执行上下文还需要 instance。
html 实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#model {
width: 200px;
height: 200px;
line-height: 200px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid #000;
}
</style>
</head>
<body>
<button id="open">打开弹窗</button>
<button id="close">关闭弹窗</button>
<script>
</script>
</body>
</html>
我们这里希望,点击这个按钮可以打开和关闭弹窗。这里需要用到单例模式,因为你点击打开弹窗,会一直是之前的弹窗,而不会新建出一个新的弹窗。
来看看这里是怎么实现的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#model {
width: 200px;
height: 200px;
line-height: 200px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid #000;
transition: opacity 0.3s ease-in-out;
/* 添加过渡效果,使元素的不透明度在 0.3 秒内平滑过渡 */
opacity: 0;
/* 初始状态下元素的不透明度为 0,即完全透明 */
}
</style>
</head>
<body>
<button id="open">打开弹窗</button>
<button id="close">关闭弹窗</button>
<script>
const Model = (function () {
let model = null;
// 内部函数,用于创建或显示/隐藏弹窗
return function (flag) {
if (!model) { // 检查弹窗元素未创建
model = document.createElement('div');
model.innerHTML = '我是一个全局弹框';
model.id = 'model';
document.body.appendChild(model);
}
// 始终将元素的 display 属性设置为 block,确保元素存在但可能不可见
model.style.display = 'block';
// 根据传入的 flag 设置元素的不透明度,实现显示或隐藏
model.style.opacity = flag === 'block' ? '1' : '0';
// 返回创建或已存在的弹窗元素
return model;
}
})();
// 为打开弹窗的按钮添加点击事件监听器
document.getElementById('open').addEventListener('click', () => {
// 调用 Model 函数并传入 'block' 显示弹窗
Model('block');
});
// 为关闭弹窗的按钮添加点击事件监听器
document.getElementById('close').addEventListener('click', () => {
// 调用 Model 函数并传入 'none' 隐藏弹窗
Model('none');
});
</script>
</body>
</html>
用浏览器打开看,功能正常。