单例模式

162 阅读3分钟

单例模式

简单的单例模式

通过一个getInstance方法获取对象,首先判断一个对象是否存在,如果存在则就将该对象返回,如果不存在就先实例化对象(创建对象),然后再返回

将实例存储在对象中

let Singleton = function (name) {
    this.name = name;
}
// 将实例存储在对象中
Singleton.getInstance = function (name) {
    // 如果当前对象上不存在instance属性,那么就实例化一个对象,并且保存在属性instance上
    if (this.instance === void (0)) {
        this.instance = new Singleton(name);
    }
    // 返回该对象属性 
    return this.instance;
}
let aIns = Singleton.getInstance("test");
let bIns = Singleton.getInstance("ceshi");
console.log(aIns === bIns, aIns, bIns);

我们发现第一次将name参数传入后,后面获取再放入name参数并不会影响到原实例

使用闭包存储实例对象

// 将实例使用闭包的形式存储起来
Singleton.getInstanceNew = (function() { 
  // 存储单例对象
  let instance = null;
    
  // 返回了一个方法,这个方法引用了作用域外的变量,形成了闭包
  return function(){
    // 还是一样的操作
    if(instance === null){
      instance = new Singleton();
    }
    return instance;
  } 
}())  

let cIns = Singleton.getInstanceNew();
let dIns = Singleton.getInstanceNew();

console.log(cIns === dIns, aIns === dIns);

我们看到以上两个方法,都是创建单例的简单方法,虽然实现了,但是同时有一定缺点

  • 如果别人不知道这个对象时单例的,还是可以使用new关键字创建。
  • 当使用new创建后,实例不再是单例了,所以很容易违背了我们单例的初心

透明的单例模式

我们依然通过new创建实例,但是创建的还是单例的,这样即使不需要我们特意去嘱咐这个对象是单例

var TransparentSingletonBox = (function() {
   // 存储单例对象
   var instance = null;
    
   // 构造函数引用了作用域外的变量,形成了闭包
   var TransparentSingleton = function(){
      if(instance){
        return instance;
      }
      return instance = this;
   } 
   
  // 返回了一个构造函数
  return TransparentSingleton;
}())


let eIns = new TransparentSingletonBox();
let fIns = new TransparentSingletonBox();

console.log(eIns === fIns);
  • 先执行的是自执行函数,并返回一个构造函数TransparentSingleton
  • 这个构造函数内部引用了外部作用域【自执行的匿名函数作用域】的变量instance,形成闭包
  • 外部TransparentSingletonBox接受到内部返回的TransparentSingleton构造函数

这种方法虽然满足了我们的要求,但是这里存在一个问题,就是内部的TransparentSingleton的这对象,成为了一个私有对象,我们在外面无法访问到

代理版单例模式

var CreateDom = function(html){
  this.html = html;
  this.init();
}

CreateDom.prototype.init = function(){
  console.log(this.html);
}

var ProxyCreateDom = (function(){
  let instance = null;
  return function(html){
    if(instance === null){
      instance = new CreateDom(html);
    }
    return instance;
  }
}())

let hIns = new ProxyCreateDom("测试");
let gIns = new ProxyCreateDom("前端");
console.log(hIns === gIns, hIns.html, gIns.html)

这里我们将对象提了出来了,通过ProxyCreateDom去创建一个关于CreateDom的单例,但是这个版本是比较有限的

  • 我们可以看到这个代理单例并不是一个可复用的,代码很可能会翻倍,【一个对象对应一个代理单例】。
  • 不管我们需不要单例,其实ProxyCreateDom里的instance就创建了。
  • 这个是属于传统面向对象语言的,在JavaScript这个没有类的语言中,这样创建意义并不大【这个不是很理解,有大佬可以帮忙解释一下吗?】

惰性单例模式通用版本

let getSingleton = function(func) { 
  let result = null;
  return function(){ 
    return result || (result = func.apply(this, arguments));
  }
}

function createLoginLayer(){
  var div = document.createElement("div");
  div.innerHTML = "我是登录浮窗";
  div.style.display = "none";
  document.body.appendChild(div);
  return div;
}

/**
 * 测试代码 
 */
let createSingleLoginLayer = getSingleton(createLoginLayer); 
// 添加一个登陆浮窗
document.getElementById("loginTest").onclick = function(){
  var loginLayer = createSingleLoginLayer();
  loginLayer.style.display = "block"
}
// 创建一个iframe的单例
var createSingleIfame = getSingleton(function () {
  var iframe = document.createElement("iframe");
  document.body.appendChild(iframe);
  return iframe;
})
// 创建一个iframe,并将src属性设置为百度
document.getElementById("loginBtn").onclick = function(){
  var loginLayer = createSingleIfame();
  loginLayer.src = "http://baidu,com";
}
// 将浮窗单例隐藏
document.getElementById("closeLoginBtn").onclick = function(){
  var loginLayer = createSingleLoginLayer();
  loginLayer.style.display = "none"
}
// 改变iframe的src属性
document.getElementById("changeUrlBtn").onclick = function(){
  var loginLayer = createSingleIfame();
  loginLayer.src = "http://127.0.0.1:5500/index.html";
}