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是很慢的,所以我们创建一次之后,用户点击关闭,其实可以隐藏起来,并不是实际的关闭,这样当用户点击第二次的时候就省略了创建窗口的过程,直接渲染数据就可以。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,009评论 19 139
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,298评论 4 34
  • 1 单例模式的动机 对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子——Windows任务...
    justCode_阅读 1,483评论 2 9
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,163评论 0 21
  • 总结一下今年感觉还不错 中的
    7a433e1f1491阅读 159评论 0 0