前言
开个新坑,准备聊一下设计模式。网上很多教程、书籍还是停留在 es5,说实话有些老旧,一些类的东西现在几乎不会手动去调 property,用 class 显然更简单明了。
另外,设计模式源于面向对象的语言,而前端的 JS 还是偏向于面向过程。如果直接生搬硬套类的方案,只会不伦不类,我们的项目中几乎用不到。
我更希望能学以致用,而非单纯学习理论知识。
所以本系列会更多的加入自己的思考和观点,可能设计模式并没有书上那么标准,但是会更容易应用到我们的日常工作中。
单例模式
假设有这样一个场景,点击按钮,打开对话框
实现它很简单,先写好一个对话框,并将其隐藏掉,用点击目标按钮来显示对话框
<button id="open-dialog-btn">打开弹窗</button>
<div id="dialog" style="display: none;">
<p>这是一个简单的弹窗。</p>
</div>
<script>
const dialog = document.getElementById("dialog");
document.getElementById("open-dialog-btn").addEventListener("click", () => {
dialog.style.display = "block";
});
document.getElementById("close-dialog-btn").addEventListener("click", () => {
dialog.style.display = "none";
});
</script>
但是如此一来,会导致一进入页面就需要加载这个模态框。
如果我们完全不需要点击它,那这里的加载就是完全浪费资源的,我们最好将其改造为点击后再进行渲染的方案。
<button id="open-dialog-btn">打开弹窗</button>
<script>
document.getElementById("open-dialog-btn").addEventListener("click", () => {
if (!document.getElementById("dialog")) {
const dialog = document.createElement("div");
dialog.id = "dialog";
dialog.innerHTML = `<p>这是一个简单的弹窗。</p>`;
document.body.appendChild(dialog);
}
});
</script>
好的,那么问题就来了。当你点击第二次的时候,这里就又需要再次创建一个弹窗,若是 n 次点击,那就要消耗 n 倍的资源。
也许已经有同学想到了,面对这种情况,我们可以选择在创建前进行检测。若是节点已经创建,那么我们只需要将其显示出来即可
<button id="open-dialog-btn">打开弹窗</button>
<script>
let dialog = null;
document.getElementById("open-dialog-btn").addEventListener("click", () => {
if (!dialog) {
// 如果对话框尚未创建,进行创建
dialog = document.createElement("div");
dialog.id = "dialog";
dialog.innerHTML = `
<p>这是一个简单的弹窗。</p>
<button onclick="dialog.style.display = 'none'">关闭</button>
`;
document.body.appendChild(dialog);
} else {
// 如果对话框已经存在,只是显示它
dialog.style.display = "block";
}
});
</script>
很好,或许你没有意识到,你已经完成了一个单例模式 🎉🎉🎉
单例模式:
- 特点:保证仅有一个实例,并提供一个访问它的全局访问点。
- 用途:
Axios 实例、Redux Store、缓存池等- 实现:本质就是将实例对象记录起来,下次再创建的时候直接给旧的而非新建
回到刚才的代码上,目前的dialog方案还不够好,这会产生一个额外的全局变量,而且可能会被其他的地方误用。
有没有办法将其隐藏起来呢?有的兄弟有的,用闭包就行了。
<button id="open-dialog-btn">打开弹窗</button>
<script>
function createDialog() {
let dialog = null; // 利用闭包将其封住
return function () {
if (!dialog) {
// 只创建一次弹窗
dialog = document.createElement("div");
dialog.id = "dialog";
dialog.innerHTML = `
<p>这是一个简单的弹窗。</p>
<button onclick="this.parentElement.style.display = 'none'">关闭</button>
`;
document.body.appendChild(dialog);
} else {
// 如果已经存在,直接显示
dialog.style.display = "block";
}
};
}
const showDialog = createDialog();
document.getElementById("open-dialog-btn").addEventListener("click", showDialog);
</script>
非常漂亮,最后,我们或许可以稍微抽象一下,将这套单例模式变得更加可复用。于是我们的单例模式就设计好了:
function createSingletonDialog(content) {
let dialog = null;
return function () {
if (!dialog) {
dialog = document.createElement("div");
dialog.innerHTML = `
<div>
${content}
<button onclick="this.parentElement.style.display = 'none'">关闭</button>
</div>
`;
document.body.appendChild(dialog);
} else {
dialog.style.display = "block";
}
};
}
来应用一下试试,使用这段单例模式函数,我们就能写出以下 漂亮优雅 的代码
<button id="info-btn">显示信息弹窗</button>
<button id="warn-btn">显示警告弹窗</button>
<button id="error-btn">显示错误弹窗</button>
<script>
function createSingletonDialog(content) {
let dialog = null;
return function () {
if (!dialog) {
dialog = document.createElement("div");
dialog.innerHTML = `
<div>
${content}
<button onclick="this.parentElement.style.display = 'none'">关闭</button>
</div>
`;
document.body.appendChild(dialog);
} else {
dialog.style.display = "block";
}
};
}
// 创建不同类型的弹窗
const showInfoDialog = createSingletonDialog("<p>这是一个信息弹窗。</p>");
const showWarnDialog = createSingletonDialog("<p>这是一个警告弹窗。</p>");
const showErrorDialog = createSingletonDialog("<p>这是一个错误弹窗。</p>");
// 绑定点击事件
document.getElementById("info-btn").addEventListener("click", showInfoDialog);
document.getElementById("warn-btn").addEventListener("click", showWarnDialog);
document.getElementById("error-btn").addEventListener("click", showErrorDialog);
</script>
其他
我们回看一下整体的代码,其实并没有用传统的 class 来实现,比如这个标准的单例模式:
class Singleton {
static instance = null;
constructor(data) {
if (Singleton.instance) return Singleton.instance;
this.data = data;
Singleton.instance = this;
}
}
let ob1 = new Singleton("one");
let ob2 = new Singleton("two");
let ob3 = new Singleton("three");
ob2.name = 'ob2';
console.log(ob1 === ob2); // true
console.log(ob1 === ob3); // true
console.log(ob1); // Singleton { data: 'one', name: 'ob2' }
console.log(ob2); // Singleton { data: 'one', name: 'ob2' }
console.log(ob3); // Singleton { data: 'one', name: 'ob2' }
这样的应用面就太窄了,很难落地在项目中。
相反,用js更擅长的面向过程编程,反而能更好应用。
当你项目中需要使用全局唯一的变量时,不妨回头看本篇文章的思路。
借用一个大佬发言:我写代码会尽可能追求高逼格,让大家觉得我牛逼,这样既有成就感,又能提升技术。
如果有帮助,不妨点个赞吧~