1_单例模式

1 简介

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点

线程池、全局缓存、浏览器中的window对象

2 实现单例模式

  • 思路:用一个变量来标志当前是否已为某个类创建过对象,是则在下一次获取该类的实例时直接返回之前创建的对象
function Singleton(name) {
    this.name = name
}
Singleton.getInstance = (function() {
    let instance = null
    return function(name) {
        if(!instance) {
            instance = new Singleton(name)
        }
        return instance
    }
})()

不透明性 -- 此时通过 Obj.getInstance创建对象,但是一般是通过 new Obj 创建实例对象的

透明的单例模式

  • CreateDiv 单例类:负责在页面中创建唯一的 div 节点
let CreateDiv = (function() {
  let instance;
  class CreateDiv {
    constructor(html) {
      if(instance) {
        return instance
      }
      this.html = html;
      this.init();
      // 将创建的实例对象赋值给instance
      return instance = this
    }
    init() {
      let div = document.createElement('div')
      div.innerHTML = this.html
      document.body.appendChild(div)      
    }
  }
  return CreateDiv
})()
var a = new CreateDiv('sven1')
var b = new CreateDiv('sven2')
console.log(a === b) // true

存在的问题

  • 为了封装 instance 使用了自执行的匿名函数和闭包, 且匿名函数返回了真正的构造类 --> 复杂度增加
  • 违反单一职责原则 -- 创建节点和管理单例的逻辑都放在 CreateDiv内部

3 用代理实现单例模式

1. 将负责管理单例的代码移出

使其成为普通的创建div的类

class CreateDiv {
  constructor(html) {
    this.html = html
    this.init()
  }
  init() {
    let div = document.createElement('div')
    div.innerHTML = this.html
    document.body.appendChild(div)
  }
}

2. 定义代理类 proxySingletonCreateDiv

var proxySingletonCreateDiv = (function() {
  let instance
  return function(html) {
    if(!instance) {
      instance = new CreateDiv(html)
    }
    return instance
  }
})()

将负责管理单例的逻辑移到了代理类proxySingletonCreateDiv中。此时CreateDiv就变成了一个普通的类,它跟proxySingletonCreateDiv组合起来就可以达到单例模式的效果

3. 应用

var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
console.log(a === b)

4 惰性单例

在合适的时候才创建对象,并且只创建唯一一个

webqq登录浮窗

以webQQ的登录浮窗为例,介绍与全局变量结合实现惰性的单例

  • 点击登录弹出登录浮窗时,该浮窗在页面里总是唯一的,不可能出现同时存在两个登录窗口的情况
1. 方案1:在页面加载时便创建好浮窗,初始隐藏,用户点击登录按钮时才显示
let loginLayer = (function() {
  let div = document.createElement('div')
  div.innerHTML = '我是登录浮窗'
  div.style.display = 'none'
  document.body.append(div)
  return div
})()
document.getElementById('loginBtn').onclick = function() {
  loginLayer.style.display = 'block'
}
2. 方案2:点击登录按钮时再创建浮窗
let createLoginLayer = (function() {
  let div
  return function() {
    if(!div) {
      div = document.createElement('div')
      div.innerHTML = '我是登录浮窗'
      div.style.display = 'none'
      document.body.appendChild('div')
    }
    return div
  }
})()
document.getElementById('loginBtn').onclick = function() {
  let loginLayer = createLoginLayer()
  loginLayer.style.display = 'block'
}
存在的问题
  • 违反单一职责原则 -- 创建对象和管理单例的逻辑都放在 createLoginLayer 对象内部
  • 无通用性

    下次要创建页面唯一的script标签用来跨域请求数据,需要copy代码

5 通用的惰性单例

将不变的部分隔离出来 -- 管理单例的逻辑抽象出来:用一个变量来标志是否创建过对象,是则直接返回这个已经创建好的对象

1. 管理单例 -- getSingle()

创建对象的方法被当成参数动态传入getSingle函数

function getSingle(fn) {
  let result
  return function() {
    return result || (result = fn.apply(this, arguments))
  }
}
2. 创建对象 -- 创建浮窗的函数fn
function createLoginLayer() {
  let div = document.createElement('div')
  div.innerHTML = '我是登录浮窗'
  div.style.display = 'none'
  document.body.appendChild('div')
  return div
}
3. 应用
let createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function() {
  let loginLayer = createSingleLoginLayer()
  loginLayer.style.display = 'block'
}
posted on 2023-05-09 10:47  pleaseAnswer  阅读(18)  评论(0编辑  收藏  举报