javascript 设计模式之单例模式

概念

单例模式:确认一个类只有一个实例,并提供一个访问它的全局访问点
可以看出单例模式要满足两个条件

  • 只有一个实例
  • 可以全局访问

模式类型

实现方式:使用一个变量存储类实例对象(值初始为 null/undefined )。进行类实例化时,判断类实例对象是否存在,存在则返回该实例,不存在则创建类实例后返回。

“简单版本” 单例模式

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)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值