带你了解前端设计模式-🍀单例模式🍀
概述
单例模式 (Singleton Pattern),保证
一个类只有一个实例
,并提供一个访问它的全局访问点
。也就是说,第二次使用同一个类创建新对象的时候,应该得到与第一次创建的对象完全相同的对象。
重点
:
一个类只能生成一个实例对象
提供一个全局访问点来获取这个实例
单例模式为了限制对单例实例的访问,且保证所有代码使用同一个实例。
现在我们想一下在前端应用中,符合上述两点的,有哪些应用:
- 全局的window和document对象
- Vuex,Redux或者Router
- 全局loading
- .......
那使用单例模式有什么好处
呢?
在编程中也有很多对象只需要唯一一个,比如数据库连接、线程池、配置文件缓存、浏览器中的 window/document 等,如果创建多个实例,会带来
资源耗费严重
,或访问行为不一致
等情况。
那我们在什么情况下使用单例模式
呢?
还是要根据单例模式的定义来思考。
当需要确保在整个应用中
只有一个实例存在,并且该实例需要被全局访问时,单例模式是一个不错的选择
。然而,需要谨慎使用单例模式,因为它可能会引入全局状态,增加代码的复杂性和耦合度
。
单例模式的实现
单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来访问该实例。它在许多情况下都很有用,例如管理全局状态、共享资源、数据库连接等。以下是几种实现单例模式的常见方法:
构造函数
使用构造函数实现单例模式可以通过在构造函数内部进行实例化控制来确保只有一个实例被创建,并且提供一个静态方法或属性来获取该实例。以下是使用构造函数实现单例模式的示例:
let singleDemo;
function SingleDemo() {
if (!singleDemo) {
singleDemo = this;
}
return singleDemo;
}
SingleDemo.prototype.show = function () {
console.log("我是单例模式");
};
const single1 = new SingleDemo();
const single2 = new SingleDemo();
console.log(single1 === single2);
使用类的静态属性
在 JavaScript 中,从ES6开始,我们可以使用类的静态属性来定义一个类级别的属性。这个属性将与类本身相关联,而不是与类的实例相关联。
可以使用类的静态方法来实现单例模式。以下是一个使用静态类方法实现单例模式的示例:
class Singleton {
static instance;
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
使用闭包
闭包的三个基本特点
1.闭包可以访问外部函数的变量,即使外部函数已经返回了
2.闭包保存外部函数变量的引用,而不是实际的值
3.每当一个函数在另一个函数中被创建时,就回产生闭包。
const Singleton = (function () {
let instance;
function createInstance() {
// 实例化逻辑
return {
// 实例属性和方法
};
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
应用场景
常见的单例(而非单例模式的应用):
浏览器中的 window 和 document 全局变量
,这两个对象都是单例,任何时候访问他们都是一样的对象,window 表示包含 DOM 文档的窗口,document 是窗口中载入的 DOM 文档,分别提供了各自相关的方法。在 ES6 新增语法的 Module 模块特性,通过 import/export 导出模块中的变量是单例的
,也就是说,如果在某个地方改变了模块内部变量的值,别的地方再引用的这个值是改变之后的。项目中的全局状态管理模式
Vuex维护的全局状态,
vue-router`维护的路由实,在单页应用的单页面中都属于单例的应用。模态框/弹窗管理器
:确保在同一时间只有一个模态框或弹窗处于打开状态。
React 的状态管理
在 React 应用中,使用单例模式的一个常见场景是状态管理。通常情况下,你可以使用库如 Redux 或 MobX 来管理应用的状态,它们本质上也是单例模式的实现。
Redux 的 Store
:Redux 中的 Store 对象是一个单例,它包含了整个应用的状态树,并且在整个应用中是唯一的。通过 Redux 提供的 API,可以对这个全局状态进行操作和访问。MobX 的 Store
:类似地,MobX 也可以创建一个单例的状态存储对象,用于管理应用的状态,并确保在整个应用中是唯一的。
这些状态管理工具利用了单例模式的思想,帮助我们在 React 应用中更好地管理和共享状态。当需要在 React 应用中跨组件共享状态、统一管理状态更新时,可以考虑使用这些状态管理库,并将其视为单例对象来管理应用的状态。
全局Loading
ElementUI 中的全屏 Loading 蒙层调用有两种形式:
- 指令形式:Vue.use(Loading.directive)
- 服务形式:Vue.prototype.$loading = service
指令形式注册的使用方式 :
<div :v-loading.fullscreen="true">...</div>;
服务形式注册的使用方式 :
this.$loading({ fullscreen: true });
用服务方式使用全屏 Loading 是单例的,即在前一个全屏 Loading 关闭前再次调用全屏 Loading,并不会创建一个新的 Loading 实例,而是返回现有全屏 Loading 的实例。
下面是 ElementUI 实现全屏 Loading 的源码:
import Vue from 'vue'
import loadingVue from './loading.vue'
const LoadingConstructor = Vue.extend(loadingVue)
let fullscreenLoading
const Loading = (options = {}) => {
if (options.fullscreen && fullscreenLoading) {
return fullscreenLoading
}
let instance = new LoadingConstructor({
el: document.createElement('div'),
data: options
})
if (options.fullscreen) {
fullscreenLoading = instance
}
return instance
}
export default Loading
这里的单例是 fullscreenLoading,是存放在闭包中的,如果用户传的 options 的 fullscreen 为 true 且已经创建了单例,则直接返回之前创建的单例,如果之前没有创建过,则创建单例并赋值给闭包中的 fullscreenLoading 后返回新创建的单例实例。
这是一个典型的单例模式的应用,通过复用之前创建的全屏蒙层单例,不仅减少了实例化过程,而且避免了蒙层叠加蒙层出现的底色变深的情况。
全局方法/常量
全局方法
: 通过使用单例模式,你可以创建一个包含全局方法的对象,并确保在整个应用程序中只存在一个该对象的实例。这样可以保证全局方法在整个应用程序中的唯一性,类似于单例模式的概念。
全局常量
: 同样地,通过使用单例模式,你可以创建一个包含全局常量的对象,并确保在整个应用程序中只存在一个该对象的实例。这样可以保证全局常量在整个应用程序中的唯一性,也类似于单例模式的概念。
虽然全局方法和常量本身并不是单例模式,但通过利用单例模式的思想来确保它们的唯一性,可以帮助你更好地管理和共享全局方法和常量,从而避免重复定义和管理的问题。
优缺点
js函数经过多次实例化,俊辉占用内存空间来存储各自的状态数据和方法,以便多次服用;而单利模式只创建一次实例,占用空间更小。
优点
节约开支,提高性能
: 单例模式在创建后在内存中只存在一个实例,节约了内存开支和实例化时的性能开支,特别是需要重复使用一个创建开销比较大的类时,比起实例不断地销毁和重新实例化,单例能节约更多资源,比如数据库连接;解决资源多重占用
: 单例模式可以解决对资源的多重占用,比如写文件操作时,因为只有一个实例,可以避免对一个文件进行同时操作;提高系统流畅度
: 只使用一个实例,也可以减小垃圾回收机制 GC(Garbage Collecation) 的压力,表现在浏览器中就是系统卡顿减少,操作更流畅,CPU 资源占用更少;
缺点
对扩展不友好
:一般不容易扩展,因为单例模式一般自行实例化,没有接口;与单一职责原则冲突
:一个类应该只关心内部逻辑,而不关心外面怎么样来实例化;
参考文献
转载自:https://juejin.cn/post/7370994785655603227