美文网首页让前端飞
被问到设计模式不要怂:一文读懂单例模式

被问到设计模式不要怂:一文读懂单例模式

作者: mytac | 来源:发表于2020-03-21 19:03 被阅读0次

如果觉得这里显示不好,请直接阅读我的github原文

何为单例

顾名思义,就是一个类只有一个实例,并可以全局访问。比如说一个系统只有一个登录弹窗入口,这个登录弹窗就适合用单例模式设计。

如何实现

怎么在创建一个实例的时候知道,这个类在之前有没有实例?

1. flag

function A(name){
    this.name=name
}

A.instance=null

A.getInstance=function(name){
 if(!this.instance){
     this.instance=new A(name)
 }
 return this.instance
}

2. 闭包

将上例getInstance改写:

A.getInstance=(function(name){
    var instance=null
    return function(name){
        if(!instance){
            instance=new A(nanme)
        }
        return instance
    }
})()

前面两个虽能解决单例的实现,但我每次如果要想创建单例,就必须调用getInstance函数才可以,直接new毫无作用,这样写不是很龟毛吗?

3. 闭包+同名覆盖

将上例稍微改动一下,就可直接new

var A=(function(){ // 注意用var
    var instance=null
    var A=function(){
        if(instance) return instance;
        this.init()
        return instance=this
    }
    A.prototype.init=function(){
        // do something
    }
})()

这种方法虽然很妙,但可复用性不强,因为A是有两个职责的:一是进行初始化,二是单例的判断。

4. 使用代理类

顾名思义,代理类就是进行代理,把主要职责转发。这里我们把A写为普通类。

var A=function(name){
    this.name=name
    this.init()
}

A.prototype.init=function(){
    // do something
 }

 // 创建代理类
 var P=(function(){
     var instance=null
     return function(name){
         if(!instance){
            instance=new A(name)
         }
         return instance
     }
 })()

 const p1=new P('aaa')
 const p2=new P('bbb')
 alert(p1===p2) // true

在JS中的应用

由于社区更新迭代速度非常快,尤其是前端工程化出现的各种工具、框架和思想,让我们在开发的时候,对于变量的控制方面的困扰也少了很多,但我们不能仅仅做一个框架的使用者而仅仅局限于眼前的操作而已,要知道框架对于某些功能的实现也是基于底层语言的,所以搞清这些东西是非常有必要的。

我们都知道,js是一门class-free语言,他是没有“类”这个概念的,es6引入的class也只是语法糖而已,我们按照上面使用“类”的思想构造单例,相当于脱裤放屁,本身我们要做单例,只是需要一个“唯一”的对象,并且可以被全局访问到而已,而在js中创建一个对象十分容易,关于对象是否可被全局访问,当然是使用全局变量,而全局变量作为js一个经常被人诟病的东西,要如何处理使其污染最小呢?

1.命名空间

使用命名空间可以极大地减少全局变量,而且它创建起来十分简单。

const namespace={
    a:function(){},
    b:{
        c:()=>{}
    }
}

但如果是作为一个immutable这远远不够,因为即使是用const声明的对象类型,也会被不小心修改,所以就不得不将对象冻结,但棘手的是,这个对象中的原始类型是修改且扩展不了了,但对于此对象中嵌套的对象类型的属性,仍然可以被修改,如下:

const namespace={
    a:function(){},
    b:{
        c:()=>{}
    }
}

Object.freeze(namespace)
namespace.d='ddddd' // 不能扩展
namespace.b='bbbbbb' // 不能修改属性
namespace.b.c='cccccc' // 但内嵌的对象可以修改属性和扩展
console.log(namespace)
/* 输出
a: ƒ ()
b: {c: "cccccc"}
*/

那这里就不得不去将属性b进行处理,如下:

Object.freeze(namespace)
Object.defineProperty(namespace.b,'c',{
    writable:false,
    configurable:false
})
namespace.d='ddddd' // 不能扩展
namespace.b='bbbbbb' // 不能修改属性
namespace.b.c='cccccc' // 内嵌的对象也不可被修改
console.log(namespace)
/* 输出
a: ƒ ()
b: {c: ƒ}
*/

但这里有个弊端就是,我必须知道他有对象类型的属性再进行封装,如果对象属性中还有对象属性就需要一层层去递归实现,让人头大。

2.使用闭包封装私有变量

把私有变量封装在闭包的内部,只暴露一些接口。

var namespace=(function(){
    const _a=function(){}
    const _b={
        c:()=>{}
    }
    return {
        getNameSpace:function(){
            return ({
                a:_a,
                b:_b
            })
        }
    }
})()

Object.freeze(namespace)
namespace.getNameSpace='1111'
console.log(namespace.getNameSpace())
/*输出:
a: ƒ ()
b: {c: ƒ}
*/

惰性单例

惰性单例是当只有需要的时候才会创建对象实例,就比如在第一节中我们实现的那个例子:

function A(name){
    this.name=name
}

A.instance=null

A.getInstance=function(name){
 if(!this.instance){
     this.instance=new A(name)
 }
 return this.instance
}

但这是基于“类”的设计,上一节讲,在js中写这种单例模式等于脱裤放屁,那什么样的单例模式的实现才最有普适性呢?我将引入一个例子来分析。

在一个系统中,我们需要一个登录按钮,点击登录按钮需要弹出登录弹窗。

有两种设计思想可以实现,一是把弹窗组件写好先隐藏起来,之后通过改变css让其显示;二是当用户点击登录按钮时再去创建弹窗。因为本节讨论的是惰性单例,所以前者的实现不再讨论范围之内,所以我们来实现一下后者。

const createModal=function(){
    let modal=null
    return function(){
        if(!modal){
            modal=document.createElement('div')
            modal.innerHTML= `login modal`
            modal.style.display='none'
            document.body.appendChild(modal)
        }
        return modal
    }
}()

document.getElementById('loginBtn').onclick=()=>{
    const loginModal=createModal()
    loginModal.style.display='block'
}

当我多次点击登录按钮的时候,login modal只会创建一次。

createModal这个函数的职责还不够单一,要想让单例模式应用到很多地方,就要把这部分的逻辑抽出来。

const getSingle=function(fn){
    var res=null
    return function(){
        return res||(res= fn.apply(this,arguments))
    }
}

然后我们再实现那个需求:

const createLoginLayer=function(){
    const modal=document.createElement('div')
    modal.innerHTML= `login modal`
    modal.style.display='none'
    document.body.appendChild(modal)
    return modal
}

document.getElementById('loginBtn').onclick=()=>{
    const createSingleLoginModal=getSingle(createLoginLayer)
    const loginModal=createSingleLoginModal()
    loginModal.style.display='block'
}

如果还需要构造什么其他的单例组件:

const createA=function(){
    // .....
}

const createSingleA=getSingle(createA)
createSingleA()

这样就符合单一原则啦~~

当然getSingle()也不仅仅局限于创建dom,比如给元素绑定事件等功能上也可以达到一样的效果。但!不要觉得好用节省开销就处处使用这个函数,因为闭包会占用内存,进行调试也不好操作,所以有必要才可以用,不可画蛇添足。

最近在看《js设计模式与开发实践》,本文是我对此书的一些概括与扩展。下一篇文章写策略模式~~

最后请大家关注我的订阅号获得更加及时的推送~

那屋水泡

相关文章

  • 被问到设计模式不要怂:一文读懂单例模式

    如果觉得这里显示不好,请直接阅读我的github原文 何为单例 顾名思义,就是一个类只有一个实例,并可以全局访问。...

  • 单例模式

    单例设计模式是几种设计模式中比较容易理解的,手写单例模式也是面试频繁问到的。下面总结一下单例模式的几种写法: //...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • Java面试向:脑图单例模式

    单例模式,绝对是面试时被问到最多的设计模式。如果搜索网上的java单例模式,这篇文章是说得最全面和深入的: 你真的...

  • 线程安全的单例模式最优实现

    说起设计模式, 应该是面试中最长被问到的知识点, 通常最先能想到的非单例模式莫属! 单例模式从线程角度来讲一般分为...

  • python中OOP的单例

    目录 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 单例

    目标 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 设计模式

    面试中爱问到的设计模式主要是:单例模式、工厂模式、观察者模式、生产者消费者模式,简单总结一下。 1. 单例模式(S...

  • 设计模式 - 单例模式

    设计模式 - 单例模式 什么是单例模式 单例模式属于创建型模式,是设计模式中比较简单的模式。在单例模式中,单一的类...

  • 设计模式

    常用的设计模式有,单例设计模式、观察者设计模式、工厂设计模式、装饰设计模式、代理设计模式,模板设计模式等等。 单例...

网友评论

    本文标题:被问到设计模式不要怂:一文读懂单例模式

    本文链接:https://www.haomeiwen.com/subject/tfpuyhtx.html