概念
可以看出单例模式需满足两个条件
- 只有一个实例
- 可以全局访问
模式类型
"简单版本" 单例模式
class SingleObject {
constructor(name) {
this.name = name;
this.instance = null
}
getName() {
return this.name
}
static getInstance(name) {
// 判断是否已经 new 过1个实例
if (!this.instance) {
// 若这个唯一的实例不存在,则创建它并存储
this.instance = new SingleObject(name)
}
// 如果这个唯一实例已经存在,则直接返回
return this.instance
}
}
const s1 = SingleObject.getInstance("张三")
const s2 = SingleObject.getInstance("李四")
console.info(s1.getName()) // 张三
console.info(s2.getName()) // 张三
console.info(s1 == s2) // true
// 这里只能用 getInstance 才能保证单例,如果用 new SingleObject 就没办法保证了
const s3 = new SingleObject("王五")
console.info(s3.getName()) // 王五
console.info(s3 == s2) // false
在类里定义个静态方法 getIntance ,判断是否创建过实例,如果创建过则直接返回,没有则创建。 所以无论调用多少次 getIntance 返回的都是第一次创建的那个实例,自然 s1.getName 与 s2.getName 指向的 this 都是 s1
而 s3 不是通过 getIntance 创建的,是直接 new 出来的,所以跟 s1 s2 都不一样
缺点:
- 这种单例模式存在两种创建实例方式,当采用 new 时就不再单例了
- 判断是否实例化过(即单例判断)跟对象创建都放在 getIntance 处理,不符合单一职责原则
闭包版单例模式
const SingleObject = (function () {
//在闭包里面定义了一个instance变量,将instance变量定义在外部会污染全局变量
let instance = null
return function (name) {
// 判断变量是否为 null
if (!instance) {
this.name = name
instance = this
}
return instance
}
})()
SingleObject.prototype.getName = function () {
return this.name
}
const s1 = new SingleObject("张三")
const s2 = new SingleObject("李四")
console.info(s1.getName()) // 张三
console.info(s2.getName()) // 张三
console.info(s1 == s2) // true
const s3 = new SingleObject("王五")
console.info(s3.getName()) // 张三
console.info(s3 == s2) // true
通过立即执行函数,以及闭包创建了单例函数,这下就又统一成以 new 的形式创建对象即可
“代理版” 单例模式
简单版本单例模式将单例管理与对象创建全整在一个类里,不符合单一职责原则,而代理版的作用就是将这两者分离
class SingleObject {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
const ProxySingleObject = (function () {
let instance
return function (name) {
if (!instance) {
instance = new SingleObject(name)
}
return instance
}
})()
这样, SingleObject 只负责该类相关的属性跟方法, 至于管理单例就由 ProxySingleObject 处理,对外也只暴露 ProxySingleObject 即可。可以配合模块化,只 export ProxySingleObject ,具体见代码
测试:
import ProxySingleObject from './代理单例模式'
const s1 = new ProxySingleObject("张三")
const s2 = new ProxySingleObject("李四")
console.info(s1.getName()) // 张三
console.info(s2.getName()) // 张三
console.info(s1 == s2) // true
const s3 = new ProxySingleObject("王五")
console.info(s3.getName()) // 张三
console.info(s3 == s2) // true
惰性单例模式
利用自执行函数在代码执行时就把对象实例创建,这种有些不妥,像是弹出登录框,有可能根本就不需要登录即可访问首页。
这时我们并不需要在页面加载时就去创建一个弹窗。我们大可在需要用的时候才去创建
现在就让我们实现下登录弹框
<style>
#modal {
height: 200px;
width: 200px;
line-height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid black;
text-align: center;
}
</style>
<body>
<button id='login'>登录</button>
</body>
<script>
var createLoginModal = (function () {
let modal = null
return function () {
if (!modal) {
modal = document.createElement('div')
modal.innerHTML = "登录框"
modal.id = "modal"
modal.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
document.getElementById("login").onclick = function () {
var modal = createLoginModal()
modal.style.display = 'block'
}
</script>
通用惰性单例模式
上面惰性单例模式,也是存在单例控制以及创建融合在一起的问题,另外也不够通用。何为通用呢? 就是将单例控制抽离成单独的函数,控制保证只有一个对象,类似这样的:
var obj
if (!obj) {
obj = xxx
}
将代码修改下:
把这个操作的逻辑封装到一个 getSingle 函数中,然后把要执行的函数当作参数传入进去:
function getSingle(fn) {
let result
return function() {
result || (result = fn.apply(this,argumments))
}
}
这样我们上面写的创建弹窗的方法就可以完全抽离出来:
var createLoginModal = function () {
var modal = document.createElement('div')
modal.innerHTML = "登录框"
modal.id = "modal"
modal.style.display = 'none'
document.body.appendChild(modal)
return modal
}
var createSingleLoginModal = getSingle(createLoginModal)
document.getElementById('login').onclick = function() {
var modal = createSingleLoginModal()
modal.style.display = 'block'
}
这样,后续再有其他要实现单例的,只要将函数作为参数传入即可,比如购物车,都可以调用 getSingleton(...) 生成对应实例对象的方法
单例模式使用场景
引用第三方库(多次引用只会使用一个库引用,如 jQuery)
//简单模拟JQuery中为实现单一$,所做的单例模式判断
if(!window.Jquery){
return window.Jquery()
}else{
//其他代码
}
全局态管理 store (Vuex / Redux)
let Vue // 这个Vue的作用和上面的instance作用一样
...
export function install (_Vue) {
// 判断传入的Vue实例对象是否已经被install过Vuex插件(是否有了唯一的state)
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
// 若没有,则为这个Vue实例对象install一个唯一的Vuex
Vue = _Vue
// 将Vuex的初始化逻辑写进Vue的钩子函数里
applyMixin(Vue)
}
参考链接
JavaScript 设计模式核⼼原理与应⽤实践
结语
你的点赞是对我最大的肯定,如果觉得有帮助,请留下你的赞赏,谢谢!!!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!