前言
你是否曾经遇到过这样的场景:当你登录一个网站时,忽然弹出一个窗口让你输入账号密码;或者当你进行某项操作时,系统提示你操作错误,一个对话框跳了出来。这些弹出的窗口就是我们常说的模态框(Modal) 。模态框是一种常见的用户界面元素,它能够将用户的注意力集中在特定的任务上,提供一种沉浸式的交互体验。
然而,模态框的设计和实现对用户体验有着深远的影响。试想一下,如果在一个页面上同时弹出多个模态框,用户可能会感到困惑和不知所措。 这种混乱不仅会降低任务完成的效率,还可能导致用户流失。因此,确保在同一时间只显示一个模态框,是提升用户体验的关键之一。
为了实现这一目标,开发者常常采用一种设计模式——单例模式(Singleton Pattern)。单例模式确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。 通过这种方式,我们可以保证在任何时刻都只有一个模态框处于活动状态,从而避免了用户面对多个弹出窗口时的混乱情况。
本文将深入探讨如何利用单例模式来实现模态框的唯一性,并通过一道题目来手写单例:
实现Storage,使得该对象为单例,基于LocalStorage进行封装,实现方法setItem(key,value) 和 getItem(key)
单例模式
接下来我们先来介绍一下单例模式:
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。 这种模式通常用于控制资源的共享,例如数据库连接、日志文件或配置设置。(也就是说全局实例唯一)
单例模式的核心思想是通过以下三个步骤来实现:
- 私有构造函数:确保类的构造函数是私有的,防止外部直接创建实例。
- 静态实例:使用一个静态变量来存储唯一的实例。
- 全局访问点:提供一个公共的静态方法来获取该实例。
这里这三条规则其实就规定了两点:
- 只有通过这一个类,才能创建实例
- 我们不能在外面创建实例,只能通过方法访问实例
现在我们的思路就清晰了,就知道该怎么手写一个单例LocalStorage了
手写 LocalStorage
class 实现法
首先我们创建一个LocalStorage类:
class Storage{
constructor(){
}
}
为了确保只能通过这个类来创建实例,我们可以在类中定义一个静态私有变量来存储这个唯一的实例:
class Storage{
static instance;
constructor(){
}
}
接下来遵从单例模式的核心思想,我们只能从类里的静态方法中拿到这个实例,那就应该有一个静态方法返回这个实例:
class Storage{
static instance;
constructor(){
}
static getInstance(){
}
}
我们该如何保证这个instance变量是唯一的呢?当它不存在的时候我们就创建一个实例并赋值给instance,然后我们返回它;那当它存在的时候呢?我们如何返回已有的实例呢? 是不是直接返回instance就好了呀~
class Storage{
static instance;
constructor(){
}
static getInstance(){
if(!Storage.instance){
Storage.instance = new Storage();
}
return Storage.instance;
}
}
这样的话,就算没有instance也会先创建再返回,有的话就会跳过这个判断直接返回同一个实例了。由此我们实现了单例模式。
接下来就是给它加上两个方法:setItem和getItem:
class Storage {
static instance;
constructor() {
}
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
getItem(key) {
return localStorage.getItem(key);
}
setItem(key, value) {
return localStorage.setItem(key, value)
}
}
注意:这里的 localStorage是浏览器的一个API,我们可以用它在存储中添加一些信息,所以这个函数不能在node.js中被调用,node.js并没有这样的功能,所以这一段要在浏览器中实现。
接下来我们可以验证一下先后创建的是不是同一个实例(以下在浏览器中运行):
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
console.log(storage1 === storage2); // true
storage1.setItem('try to catch me', 'blocking sight') // 前面是key 后面是value
console.log(storage1.getItem('try to catch me')); // 'blocking sight'
OK,你已经很棒了,你用class实现了单例模式,但是在ES6之前,JavaScript可是没有class关键字的哦,那么该怎么实现呢?
function 实现单例
与class相比,我们要创建单例面临着一些挑战:
我们没有类这个概念
只能通过一个静态方法访问获得实例,而且要保证实例的安全性(不能随意被外界访问)
这个LocalStorage的方法如何绑定到我们的实例上?
带着这些问题,我来答疑解惑咯(百度百科赞助装杯.jpg)~
OK,只要我们解决了这些问题,单例就写出来了,第一个,如何处理类这个概念?
在没有类的时候我们都怎么创建对象呢?
答案是构造函数。
so,我们可以创建一个构造函数,模拟类:
function StorageBase(){
// 因为没有什么好初始化的,所以不用写如何构造
}
接下来就是第二个难点,如何通过一个方法得到实例,且保证实例的安全性?
那我们就得创建一个函数了,通过访问这个函数来创建实例,这个倒是不难吧!
但是如何确保实例的安全性呢? 有什么能让这个实例不容易被外界访问,变得私密一些?
没错!就是利用闭包!这样只有那个特定的函数可以访问到它!
const createInstance = (function(){
let instance = null;
return function(){
if(!instance){
instance = new StorageBase();
}
return instance;
}
})() // 利用IIFE创建闭包
哎呀呀,我们真是太厉害了(我太厉害了23333~),接下来就剩最后一个问题需要解决了,如何能让instance利用这些方法呢?
instance是StorageBase函数的一个实例哦~
怎么能让instance使用StorageBase的方法呢?
没错!就是prototype!
new操作后instance的__proto__绑定到了StorageBase.prototype上,还记得吗?
所以我们就在它的原型链上定义方法即可:
StorageBase.prototype.getItem = function (key) {
// 利用这个方法会直接调用函数,而localStorage.getItem(key)没有返回值
// 所以将 return 去掉也不会有什么影响
return localStorage.getItem(key)
}
StorageBase.prototype.setItem = function (key, value) {
localStorage.setItem(key, value)
}
完整代码如下:
function StorageBase() {
}
StorageBase.prototype.getItem = function (key) {
// 利用这个方法会直接调用函数,而localStorage.getItem(key)没有返回值
// 所以将 return 去掉也不会有什么影响
return localStorage.getItem(key)
}
StorageBase.prototype.setItem = function (key, value) {
localStorage.setItem(key, value)
}
const Storage = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new StorageBase();
}
return instance
}
})()
const Storage1 = new Storage();
const Storage2 = new Storage();
console.log(Storage1 === Storage2);
Storage1.setItem('Valorant', 'Fade')
console.log(Storage2.getItem('Valorant'))
创建唯一的模态框
如何创建唯一的模态框呢?自然也是利用这个单例啦!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modal 登录弹窗单例</title>
<style>
#modal {
width: 200px;
height: 200px;
line-height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid black;
text-align: center;
}
</style>
</head>
<body>
<!-- <div class="box" style="display: none;"></div> -->
<button id="open">打开弹窗</button>
<button id="close">关闭弹窗</button>
<button id="open2">打开弹窗2</button> <!--初始化一下,做一点样式和功能,直接CV即可-->
<script>
const Modal = (function () {
let modal = null;
return function () {
if (!modal) {
modal = document.createElement('div');
modal.innerHTML = '我是全局唯一Modal'
modal.id = 'modal';
modal.style.display = 'none';
document.body.appendChild(modal);
// 创建唯一的模态框和内容,将其添加到body
}
return modal;
}
})()
document.getElementById('open').addEventListener('click', () => {
const modal = new Modal();
modal.style.display = 'block'
})
document.getElementById('open2').addEventListener('click', () => {
const modal = new Modal();
modal.style.display = 'block'
})
document.getElementById('close').addEventListener('click', () => {
const modal = new Modal(); // new Modal 确保拿到的是唯一一个Modal
modal.style.display = 'none'
})
// 利用按钮打开或者关闭模态框
</script>
</body>
</html>
这样一个唯一的模态框就做好了!
结语
单例模式是一个很重要的设计模式,它保证了一个类对应一个实例,保证了实例的唯一,在前端中,确保模态框等元素唯一是用户体验很重要的一环,大家跟着我的思路来,应该问题不大!好了,今天就到这里了,如果有讲的不清楚的地方,欢迎私信或者评论哦!