JavaScript设计模式二(单例模式)

JavaScript设计模式二(单例模式)

这边文章主要是JavaScript中的单例模式
定义:

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

其实我们的日常开发中或多或少的用到了单例模式的方法。例如我们做Electron开发的过程中,点击一个按钮创建了一个窗口,后续点击的时候,如果窗口已经存在了就focus窗口,否则创建;或者我们经常会创建一个定时任务,同时把定时任务赋值给一个变量,如果变量不存在就创建,否则不创建。但是大多数情况我们都是利用的是一个变量来控制的。接下来我们看看代码的实现

实现单例模式

其实上面的介绍我们已经说了实现单例模式的思路,就是通过一个变量来控制

var Singleton=function(name) {
    this.name=name;
    this.instance=null;
}
Singleton.prototype.getName=function(){
    console.log(this.name);
}
Singleton.getInstance=function(name) {
    if(!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
}

//这种写法借助了this.instance,其实可以不需要

var Singleton = function(name) {
    this.name=name;
}
Singleton.prototype.getName=function(){
    console.log(this.name);
}
Singleton.getInstance=(function(){
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    };
})();

使用方法:

var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
a===b

这种方式实现了我们说的单例模式,但是有一个很明显的缺点,就是我们实例化的时候不是使用的new方法来实例化,而是用的getInstance方法,也就是说我们必须知道这个类是单例类,才能这样去用,这就增加了不透明性。

透明的单例模式

所谓的透明的单例模式,就是我们可以像正常的类那样去new一个单例类。

var CreateDiv = (function(){
    var instance;
    var CreateDiv = function(html) {
        if (instance) {
            return instance;
        }
        this.html = html;
        this.init();
        return instance = this;
    };
    
    CreateDiv.prototype.init = function() {
        var div = document.createElement('div');
        div.innerHTML = this.html;
        document.body.appendChild('div');
    };
    
    return CreateDiv;
})();

var a = new CreateDiv('div');
var b = new CreateDiv('div');

a===b

之所以能够通过new来创建一个单例类的实例,是因为CreateDiv
的返回值是一个构造函数,这个构造函数做了两件事情

  • 创建对象和执行init方法
  • 保证只有一个对象

我们可以想象如果需求变成了,我们需要CreateInput之类,是不是一直要修改这个类呢?

代理实现单例模式

利用代理就可以很好的解决上面的问题

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('div');
var b = new ProxySingletonCreateDiv('div');

利用代理类,我们遵循了单一职责的原则,让代理类负责单例的逻辑,CreateDiv变成一个普通的创建html的类,两者结合达到单例模式的效果

JavaScript中的单例模式

JavaScript单例模式的核心是:

确保只有一个实例,并提供全局访问

与传统的面向对象语言不一样,JavaScript可以定义全局变量,而且我们通常认为全局变量就是一个单例,但是这种使用方式很容易造成命名空间的污染,针对这种问题有两种办法

  • 使用命名空间
    最简单的就是使用字面量常量:
var namespace1 = {
    a: function(){},
    b: 'bbbb'
}
  • 利用闭包封装变量

var user = (function(){
    
    var _user = 'hahaha';
    return {
        setUserName: function(name) {
            _user = name;
        },
        getUserName: function(){
            console.log(_user);
        }
    }
})();

惰性单例

定义:

惰性单例是指需要时才创建的单例

上面的那几种方法实际上就是惰性单例,但是那些事面向对象的,我们看看JavaScript中的惰性单例,看一段PC版的代码吧

let historyWindow = null;
ipc.on(cfg.CHANNEL.LOCAL.CHAT.SEARCH_HISTORY, function(event, arg) {
  if (!historyWindow) {
    historyWindow = new BrowserWindow({
      width: 621,
      height: 540,
      minWidth: 621,
      minHeight: 540,
      frame: false,
      show: false,
    });
    historyWindow.setAutoHideMenuBar(false);
    historyWindow.loadURL('file://' + __dirname + '/../../../client/views/history.html');
    if (process.env.NODE_ENV == 'dev') {
      historyWindow.webContents.openDevTools();
    }
    historyWindow.on('close', function() {
      historyWindow.webContents.closeDevTools();
    });
    historyWindow.on('closed', function() {
      glb.set(cfg.GLB.HISTORY_WINDOW, null);
      glb.remove(cfg.GLB.ADD_MEMBER_WINDOW);
      historyWindow = null;
    });
    historyWindow.webContents.on('did-finish-load', function() {
      glb.set(cfg.GLB.HISTORY_WINDOW, historyWindow);
      historyWindow.show();
      historyWindow.focus();
      historyWindow.webContents.send(cfg.CHANNEL.LOCAL.CHAT.OPEN_HISTORY_WINDOW_RECV, arg);
    });
  } else {
    historyWindow.webContents.send(cfg.CHANNEL.LOCAL.CHAT.OPEN_HISTORY_WINDOW_RECV, arg);
    if (historyWindow.isMinimized()) {
      historyWindow.show();
    } else {
      historyWindow.focus();
    }
  }
});

这段代码的逻辑是,点击历史消息的icon,创建一个历史消息的弹出框,后面如果继续点击,就把之前的弹出框focus。

这里其实还有优化的空间,我们知道electron创建一个新的BrowserWindow是很慢的,所以我们创建一次之后,用户点击关闭,其实可以隐藏起来,并不是实际的关闭,这样当用户点击第二次的时候就省略了创建窗口的过程,直接渲染数据就可以。

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