js设计模式之单例模式

在读这篇文章之前,也许你对单例模式的概念感到模糊或者不清楚,但是其实在日常的开发中你肯定用到过单例模式

那么接下来主要按照以下步骤进行讲述JS设计模式-------单例模式

  • 什么是单例模式
  • 如何实现单例模式
  • 何时会用到单例模式
  • 单例模式的优缺点

1. 什么是单例模式

单例模式也称为单体模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点

举个栗子:一个班级只有一个班主任,只有一个太阳,一个国家只有一个主席这些 “唯一” “便于访问(全局访问)” 的行为对象便称作是单例

2. 如何实现单例模式

  • JavaScript`中的全局对象

    全局对象是最简单的单例模式,利用ES6let不允许重复声明的特性,刚好符合这两个特点

    //举个栗子
    let obj = {
        name:"我是单例模式",
        getName:function(){}
    }
    

    但是我们并不建议这么实现单例,因为全局对象/全局变量会有一些弊端:

    1. 污染命名空间(容易变量名冲突)
    2. 维护时不容易管控 (搞不好就直接覆盖了)

    注:单例不能够,乱用如果不满足 “唯一” 和 “全局访问”的 对象 千万不用成了单例

  • 单例设计模式的实现:面向对象

// 单例设计模式的实现:面向对象
let Singleton = function(name) {
  this.name = name;
  this.instance = null;
}
Singleton.prototype.getName = function(){
  return this.name;
}
Singleton.getInstance = function(name) {
  if(!this.instance) {
    this.instance = new Singleton(name);
  }
  return this.instance;
}

let instance1 = Singleton.getInstance('why');
let instance2 = Singleton.getInstance('www');
console.log(instance1===instance2); // 输出true

let obj1 = new Singleton('why');
let obj2 = new Singleton('www');
console.log(obj1.getName());        // 输出why
console.log(obj2.getName());        // 输出www
  • 单例设计模式的实现:闭包
// 单例设计模式的实现:闭包
let Singleton = function(name) {
  this.name = name;
}
Singleton.prototype.getName = function() {
  return this.name;
}
Singleton.getInstance = (function() {
  let instance = null;
  return function(name) {
    if(!instance) {
      instance = new Singleton(name)
    }
    return instance;
  }
})()

let instance1 = Singleton.getInstance('why');
let instance2 = Singleton.getInstance('www');
console.log(instance1 === instance2); // 输出true
  • 透明的单例设计模式

无论以上面向对象的单例实现还是闭包的单例实现,都通过Singleton.getInstance来获取Singleton类的唯一对象,这增加了这个类的不透明性,使用者必须知道Singleton是一个单例类,然后通过Singleton.getInstance方法才能获取单例对象,要解决这一问题,可以使用透明的单例设计模式

// 透明的单例模式
let CreateDiv = (function(){
  let instance = null;
  let CreateDiv = function(html) {
    if(instance) {
      return instance;
    }
    this.html = html;
    this.init();
    instance = this;
    return instance;
  }
  CreateDiv.prototype.init = function() {
    let div = document.createElement('div');
    div.innerHTML = this.html;
    document.body.appendChild(div);
  }
  return CreateDiv;
})()

let instance1 = new CreateDiv('why');
let instance2 = new CreateDiv('www');
console.log(instance1===instance2); // 输出true
  • 代理实现单例模式

虽然上述透明的单例设计模式解决了不用通过Singleton.getInstance来获取单例类的唯一对象,但是在透明的单例设计模式中,构造函数CreateDiv违反了单一职责,它不仅要负责创建对象,而且还要负责保证单例,假如某一天需求变化了,不再需要创建单例的div,则需要改写CreateDiv函数,解决这种问题,可以使用代理来实现单例模式

// 用代理实现单例模式
let CreateDiv = function(html) {
  this.html = html;
  this.init();
}
CreateDiv.prototype.init = function() {
  let div = document.createElement('div');
  div.innerHTML = this.html;
  document.body.appendChild(div);
}
let ProxyCreateDiv = (function(){
  let instance = null;
  return function(html) {
    // 惰性单例
    if(!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  }
})()
let divInstance1 = new ProxyCreateDiv('why');
let divInstance2 = new ProxyCreateDiv('www');
console.log(divInstance1===divInstance2); // 输出true

惰性单例模式(懒汉式)

惰性单例是指的是页面开始加载的时候我们的实例是没有进行创建的,是当我们点击页面的div之后才开始创建实例(按需创建),这可以提高我们的网页性能,加快我们的页面渲染速度;

惰性单例又被成为懒汉式,相对应的概念是饿汉式:

  • 懒汉式单例是在使用时才实例化
  • 饿汉式是当程序启动时或单例模式类一加载的时候就被创建。

饿汉式

class Window {
  //直接进行创建
  private static instance: Window = new Window();
  public static getInstance() {
    return Window.instance;
  }
}
//把Window做成单例
let w1 = Window.getInstance();
let w2 = Window.getInstance();
console.log(w1 === w2);

惰性单例模式(懒汉式)

class Window {
    // 存储单例
    private static instance: Window;
   
    public static getInstance() {
        // 判断是否已经有单例了
        if (!Window.instance) {
            Window.instance = new Window();
        }
        //返回实例
        return Window.instance;
    }
}
//把Window做成单例
let w1 = Window.getInstance();
let w2 = Window.getInstance();
console.log(w1 === w2);//true

3. 何时会用到单例模式

  • 经常会有这样的场景页面多次调用都有弹窗提示,只是提示内容不一样

这个时候我们可以立马想到是单例模式,弹窗就是单例实例,提示内容是参数传递;我们可以用惰性单例模式来实现它;


  let getSingleton = function (fn) {
    let result;
    return function () {
      return result || (result = fn.apply(this, arguments)); // 确定this上下文并传递参数
    }
  }
  let createAlertMessage = function (html) {
    let div = document.createElement('div');
    div.innerHTML = html;
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
  }
  let createSingleAlertMessage = getSingleton(createAlertMessage);
  document.getElementById('loginBtn').onclick = function () {
    let alertMessage = createSingleAlertMessage('baidu');
    alertMessage.style.display = 'block';
  }
  • 网页登录浮窗
  // 封装单例模式
  var getSingle = function(fn) {
    var result;

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

  // 登录
  var loginMsgbox = function() {
    var dom = document.createElement('div');

    dom.innerHTML = '登录弹框';
    dom.style.display = 'none';
    document.body.appendChild(dom);

    return dom;
  }
  var createLoginMsgbox = getSingle(loginMsgbox);
  document.getElementById('loginBtn').onclick = function() {
    var login = createLoginMsgbox();

    login.style.display = 'block';
  }

当然还有用以下使用场景

  • redux中的store
  • 全局缓存
  • 浏览器中的window对象

4. 单例模式的优缺点

单例模式主要解决的问题就是节约资源,保持访问一致性。

简单分析一下它的优点:

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

单例模式也是有缺点的

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

你可能感兴趣的:(单例模式,javascript,设计模式)