单例模式(日常笔记1)_oldfour_0830_9674的博客-程序员秘密_单例模式的生活例子

技术标签: 前端  js  基础  单例模式  设计模式  javascript  

定义

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

生活中例子

当我们在电脑上玩经营类的游戏,经过一番眼花缭乱的骚操作好不容易走上正轨,夜深了我们去休息,第二天打开电脑,发现要从头玩,立马就把电脑扔窗外了,所以一般希望从前一天的进度接着打,这里就用到了存档。每次玩这游戏的时候,我们都希望拿到同一个存档接着玩,这就是属于单例模式的一个实例。

使用的场景

有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。

实现单例模式

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

{
    a:1}==={
    a:1} //false

那么问题来了,如何对构造函数使用 new 操作符创建多个对象时,仅获取同一个单例对象呢。

var Singleton = function (name) {
    
    this.name = name
}
Singleton.getInstance = (function (name) {
    
    var instance = null
    return function (name) {
    
        if (!instance) {
    
            instance = new Singleton(name)
        }
        return instance
    }
})(name)
var a = Singleton.getInstance('张三')
var b = Singleton.getInstance('李四')
console.log(a, b, a === b);

这个构造函数在内部维护(或者直接挂载自己身上)一个实例,第一次执行 new 的时候判断这个实例有没有创建过,创建过就直接返回,否则走创建流程。

透明的单例模式

虽然现在已经完成了一个单例模式的编写,但这段单例模式代码的意义并不大。从下一节开
始,我们将一步步编写出更好的单例模式。

我们现在的目标是实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使
用其他任何普通类一样。

var CreateDiv = (function () {
    
    var instance = null
    var CreateDiv = function (text) {
    
        if (instance) {
    
            return instance
        }
        this.text = text
        this.init()
        return instance = this
    }
    CreateDiv.prototype.init = function () {
    
        var div = document.createElement('div');
        div.innerHTML = this.text;
        document.body.appendChild(div);
    }
    return CreateDiv
})();
var a = new CreateDiv('我建议滑着走');
var b = new CreateDiv('sven2');
console.log(a, b, a === b);

在这段代码中,CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始 化 init 方法,第二是保证只有一个对象。

假设我们某天需要利用这个类,在页面中创建千千万万的 div,即要让这个类从单例类变成
一个普通的可产生多个实例的类,那我们必须得改写 CreateDiv 构造函数,把控制创建唯一对象
的那一段去掉,这种修改会给我们带来不必要的烦恼。

代理单例模式

  var CreateDiv = function (html) {
    
      this.html = html
      this.init()
  }
  CreateDiv.prototype.init = function () {
    
      var div = document.createElement('div')
      div.innerHTML = this.html
      document.body.appendChild(div)
  }
  // 代理类
  var ProxySingletonCreateDiv = (function () {
    
      var instance
      return function (html) {
    
          if (!instance) {
    
              instance = new CreateDiv(html)
          }
          return instance
      }
  })();
  var a = new ProxySingletonCreateDiv('我建议滑着走');
  var b = new ProxySingletonCreateDiv('sven2');
  console.log(a, b, a === b);

惰性单例模式

惰性单例指的是在需要的时候才创建对象实例。

问题场景:先有一个按钮,点击后会出现一个弹窗

  • 第一种解决方案是在页面加载完成的时候便创建好这个 div 浮窗,这个浮窗一开始肯定是隐藏状态的,当用户点击登录按钮的时候,它才开始显示。

  •        var loginLayer = (function(){
          
               var div = document.createElement('div')
               div.innerHTML = '我是登录浮窗'
               div.style.display = 'none'
               document.body.appendChild(div)
               return div
           })();
           document.getElementById('loginBtn').onclick = function(){
          
               loginLayer.style.display = 'block';
           } 
    
  • 它的缺点:用户也许不需要点击该按钮,则平白无故浪费一个dom节点。

  •         var loginLayer = function () {
          
                var div = document.createElement("div")
                div.innerHTML = "点击chuangjian"
                div.style.display = 'none'
                document.body.appendChild(div)
                return div
            }
            document.getElementById('loginBtn').onclick = function () {
          
                var creatDiv =  loginLayer()
                creatDiv.style.display = 'block';
            }
    
  • 虽然现在达到了惰性的目的,但失去了单例的效果。当我们每次点击登录按钮的时候,都会 创建一个新的登录浮窗 div。虽然我们可以在点击浮窗上的关闭按钮时(此处未实现)把这个浮
    窗从页面中删除掉,但这样频繁地创建和删除节点明显是不合理的,也是不必要的。

  •         var createLoginLayer = (function (params) {
          
                var div
                if (!div) {
          
                    var div = document.createElement('div')
                    div.innerHTML = '我是登录浮窗'
                    div.style.display = 'none'
                    document.body.appendChild(div)
                    return div
                }
                return div
            })();
            document.getElementById('loginBtn').onclick = function () {
          
                var creatDiv = createLoginLayer
                creatDiv.style.display = 'block';
            } ```
    

通用的单例模式

  • 这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在 createLoginLayer 对象内部。

  • 如果我们下次需要创建页面中唯一的 iframe,或者 script 标签,用来跨域请求数据,就
    必须得如法炮制,把 createLoginLayer 函数几乎照抄一遍。

var createIframe= (function(){
     
 var iframe; 
 return function(){
     
 if ( !iframe){
     
 iframe= document.createElement( 'iframe' ); 
 iframe.style.display = 'none'; 
 document.body.appendChild( iframe); 
 } 
 return iframe; 
 } 
})();

我们需要把不变的部分隔离出来,先不考虑创建一个 div 和创建一个 iframe 有多少差异,管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象。

var getSingle = function( fn ){
     
 var result; 
 return function(){
     
 return result || ( result = fn .apply(this, arguments ) ); 
 } 
};

接下来将用于创建登录浮窗的方法用参数 fn 的形式传入 getSingle,我们不仅可以createLoginLayer,还能传入 createScript、createIframe、createXhr 等。之后再让 getSingle 返回一个新的函数,并且用一个变量 result 来保存 fn 的计算结果。

var createLoginLayer = function () {
    
    var div = document.createElement('div')
    div.innerHTML = '我就是弹窗'
    document.body.appendChild(div)
    return div
}
  • 完整的代码
 var getSingle = function (fn) {
    
     var instance
     return function () {
    
         return instance || (instance = fn.apply(this, arguments))
     }
 }
 var createLoginLayer = function () {
    
     var div = document.createElement('div')
     div.innerHTML = '我就是弹窗'
     document.body.appendChild(div)
     return div
 }
 var createSingleLoginLayer = new getSingle(createLoginLayer)
 document.getElementById('loginBtn').onclick = function () {
    
     var loginLayer = createSingleLoginLayer();
     loginLayer.style.display = 'block';
 };

单例模式的优缺点

优点

  • 单例模式在创建后在内存中只存在一个实例,节约了内存开支和实例化时的性能开支,特别是需要重复使用一个创建开销比较大的类时,比起实例不断地销毁和重新实例化,单例能节约更多资源,比如数据库连接;
  • 单例模式可以解决对资源的多重占用,比如写文件操作时,因为只有一个实例,可以避免对一个文件进行同时操作;
  • 只使用一个实例,也可以减小垃圾回收机制 GC(Garbage Collecation) 的压力,表现在浏览器中就是系统卡顿减少,操作更流畅,CPU 资源占用更少;

缺点

  • 单例模式对扩展不友好,一般不容易扩展,因为单例模式一般自行实例化,没有接口;
  • 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化;

使用场景

  • 当一个类的实例化过程消耗的资源过多,可以使用单例模式来避免性能浪费;
  • 当项目中需要一个公共的状态,那么需要使用单例模式来保证访问一致性;

源码中的单例

以 ElementUI 为例,ElementUI 中的全屏 Loading 蒙层调用有两种形式:

  • // 1. 指令形式
    Vue.use(Loading.directive)
  • // 2. 服务形式
    Vue.prototype.$loading = service
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 后返回新创建的单例实例。
这是一个典型的单例模式的应用,通过复用之前创建的全屏蒙层单例,不仅减少了实例化过程,而且避免了蒙层叠加蒙层出现的底色变深的情况。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41163341/article/details/123430376

智能推荐

js/vue监听浏览器刷新和关闭方法_Life Free的博客-程序员秘密_js监听浏览器刷新按钮

mounted(){ window.addEventListener('beforeunload', e => this.beforeunloadHandler(e)) window.addEventListener('unload', e => this.unloadHandler(e)) }, destroyed() { window.removeEventListener('beforeunload', e => this.beforeunloadHa

Java导出excel基于velocity模板引擎_风尘小白沙的博客-程序员秘密

项目地址: GitHub https://github.com/wusas/java-excel-velocity项目结构:├─.idea│ ├─inspectionProfiles│ ├─libraries│ └─sonarlint│ └─issuestore├─.mvn│ └─wrapper├─src│ ├─main│ │ ├─java│ ...

插入排序-递归+二分查找_jiang-lc的博客-程序员秘密

title: 插入排序date: 2019-07-21 09:58:02summary: 插入排序-迭代+二分查找(Insertion-Sort)categories: 数据结构和算法tags: [LeetCode,算法导论]题目:leetcode(912): 给定一个整数数组 nums,将该数组升序排列。 示例 1: 输入:[5,2,3,1] 输出:[1,2...

lombok的使用详解,解决@Builder.Default默认值问题_颯沓如流星的博客-程序员秘密_builder默认值

前言Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCode和equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能。它所有的增强都是通过注解实现,所以了解其使用主要了解一下注解即可

程序员常用的环境变量配置_MrDawn的博客-程序员秘密

1.系统环境变量    方法一,在path里加:c:\windows\system32;c:\windows    方法二,在path里加:%SystemRoot%\system32; 2.jdk变量    如果你的JDK安装在C盘里,如:C:\Program Files\Java\jdk1.6.0_05,那么就在系统变量里(当然也可以在用户变量里)点新建:    第一步:

'javap' 不是内部或外部命令,也不是可运行的程序 或批处理文件。_Live a happy life的博客-程序员秘密

'javap' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 之前系统环境配置解决之前系统环境配置这是我之前配置环境变量。有没有发现不一样,可以和第二张图片对比一下。解决添加. ;之前我没有添加,紧跟着直接填上去的,一定要谨慎。...

随便推点

关于idea全局搜索不全的坑_ZyhMemory的博客-程序员秘密_idea搜索不全

idea全局搜索默认只显示100个,超出显示100+,不过谁能注意到这个…通过设置修改一下初始大小Help -> Find Acti在Action中输入Registry在Registry中找到ide.usages.page.size进行修改即可,我这里修改的是10000...

usb触摸屏驱动 - usbtouchscreen_AndroidBBC的博客-程序员秘密_usbtouchscreen.c

驱动编译:目前的kernel中都是自带了usbtouchscreen驱动的,我的版本3.1.10源码位于:/kernel/drivers/input/touchscreen/usbtouchscreen.c从这个路径可以看出所属驱动分支,我这边平台本身是没放开的,并没有编译进kernel,谁会想到触摸电视呢~可以在make menuconfig之后,通过Device Drivers...

转载:经典的人生总结!_雪狐的博客-程序员秘密

  一、   人的喜新最久只有三十天,所以新婚燕尔只有蜜「月」   人的忍耐最久只有三十天,所以工作以「月」薪为准   二、   结婚不是什么「人生」大事,只是合法「生人」的一道手续而已   完全相反的个性,结婚时叫「互补」;离婚时叫「个性不合」   三、   避孕的效果:不成功,便成「人」   女人的「折旧率」煞是惊人,从「新」娘变成「老」婆,只消一个晚上的光景   四、   相亲是「经销」,恋

WinForm DataGridView 鼠标点击选中整行_怒彬的博客-程序员秘密

DataGridView的SelectionMode属性改为FullRowSelectMultiSelect属性改为false就只能选中一行 

gv登陆-短信语音_zlwcm的博客-程序员秘密_gv接收短信

Google voice号码登陆我们拿到号的格式为:帐号 – 密码 – 辅助邮箱 – Google Voice号码,如图:可以登陆登陆号码的APP有两种:google voice和环聊google voice1.下载安卓手机: 有很多小伙伴问“为什么我的Google Play找不到Google Voice”或者“为什么提示此商品无法在您所在的国家/地区购买或下载”这样的问题。其实这个和苹果的账号分区差不多,并不是所有国家(区)的Google Play都有Google Voice的!Googl

计算机是如何进行计算的?(一)_了不起的盖茨比。的博客-程序员秘密_计算机如何计算

1.写在前面前面我们已经学了各种指令,我们都知道计算机的主要的工作就是收集数据,采集数据,然后处理数据,最后输出数据。但是我们还是不知道数据在计算机中如何进行计算和处理的。2.前言计算机的字由位组成,因此,字可以被表示为二进制数字。整数可以用十进制或二进制形式表示,但其他常用数字又该如何表示呢?如何表示小数和其他实数?如何运算产生了一个大到无法表示的数该如何处理?这些问题中隐藏着一个谜:硬件如何真正实现乘法或除法?3.加法和减法加法是计算机中的必备操作。数字从右到左逐位相加,并将进位传送