【读书笔记】JavaScript设计模式与开发实践--单例模式

87 阅读3分钟

定义

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

应用

线程池、全局缓存、浏览器中的window对象以及页面中只需要创建一次的元素。

实现单例模式

var Singleton = function(name){
  this.name=name
}
Singleton.instance = null;
Singleton.prototype.getName = function(){
  alert(this.name)
}
Singleton.getInstance = function(name){
  if(!this.instance){
    this.instance = new Singleton(name)
  }
  return this.instance;
}
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
a===b // true

or

var Singleton = function(name){
  this.name=name
}
Singleton.prototype.getName = function(){
  alert(this.name)
}
Singleton.getInstance = (function(){
  var instance = null;
  return function(name){
    if(!instance){
      instance = new Singleton(name)
    }
    return instance
})()
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
a===b // true

通过Singleton.getInstance来获取Singleton的唯一对象,这种方式相对简单,但是产生了一个问题:增加了类的不透明性(即使用者必须知道这是一个单例类)

透明的单例模式

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('sven1');
var b = new CreateDiv('sven2');
a===b // true

实现了一个透明单例。

缺点:

使用自执行函数和闭包,把instance封装成私有变量,匿名函数返回了真正的构造方法,增加了程序的复杂度,降低了可阅读性。

构造函数负责了两件事:创建对象并初始化 、 保证只有一个对象。不符合单一职责原则。

构造函数如果需要从单例模式变为可产生多个实例的类,需要改写构造函数。

用代理实现单例模式

// 首先将CreateDiv 变为普通类  
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('sven1');
var b = new ProxySingletonCreateDiv('sven2');
a===b // true

CreateDiv ProxySingletonCreateDiv 组合起来,缓存代理,达到了单例模式的效果。

JavaScript中的单例模式

JavaScript 中无类,创建对象的方法非常简单,创建一个唯一的对象,即为单例。

单例的核心:确保只有一个实例,并提供全局访问。

JavaScript中全局变量会被当成单例模式来使用。

var a = {}

全局变量带来的问题:命名空间污染、容易被覆盖。

作为开发者,我们有必要尽量减少全局变量的使用,即使需要,也要把它的污染降到最低。

降低全局变量带来命名污染的几种方式:

1、使用命名空间

对象字面量方式:

var namespace = {
  a:function(){},
  b:function(){},
}

动态创建命名空间:

var MyApp = {}

MyApp.namespace = function(name){
  var parts = name.split('.');
  var current = MyApp;
  for(var i in parts){
    if(!current[parts[i]]){
      current[parts[i]] = {};
    }
    current = current[parts[i]];
  }
}

MyApp.namespace('event');
MyApp.namespace('dom.style');

// 等价于
var MyApp = {
  event:{},
  dom:{
    style:{}
  }
}

2、使用闭包封装私有变量

把一些变量封装在闭包内部,暴露一些接口与外部通信,避免了对全局的命令污染。

var user = (function(){
  var __name = 'sven';
  var __age = 29;
  return {
    getUserInfo:function(){
      return __name + '-' + __age
    }
  }
})()

惰性单例

指在需要的时候才创建实例。

var createLoginLayer = (function(){
  var div ;
  return function(){
    if(!div){
      div = document.ceateElement('div');
      div.innterHTML = 'xxxxxxxxxx';
      div.style.display = 'none';
      document.body.appendChild(div)
    }
    return div;
  }
})()

// 点击登录时创建登录弹窗
document.getElementById('loginBtn').onClick = function(){
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block'
}

存在问题:违反单一职责原则,不能通用创建别的标签

通用的惰性单例

把不变的代码部分出来

var obj;
if(!obj){
  obj = xxx
}

把管理单例的逻辑从原来的代码中抽离出来

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

使用getSingle创建一个登录弹窗的实例

var createLoginLayer =function(){
  var div = document.ceateElement('div');
  div.innterHTML = 'xxxxxxxxxx';
  div.style.display = 'none';
  document.body.appendChild(div)
  return div;
}

var createSingleLoginLayer = getSingle(createLoginLayer)

// 点击登录时创建登录弹窗
document.getElementById('loginBtn').onClick = function(){
  var loginLayer = createSingleLoginLayer();
  loginLayer.style.display = 'block'
}

创建唯一的iframe

var createSingleIframe = getSingle(
  function(){
    xxx....
  }
)

创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来就完成了创建唯一实例对象的功能。

总结

单例是一种简单但很实用的模式,特别是惰性单例,合适的时候才创建对象,并且只创建唯一一个,更巧妙的是创建对象和单例管理可以写在两个方法中。