定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用
线程池、全局缓存、浏览器中的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....
}
)
创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来就完成了创建唯一实例对象的功能。
总结
单例是一种简单但很实用的模式,特别是惰性单例,合适的时候才创建对象,并且只创建唯一一个,更巧妙的是创建对象和单例管理可以写在两个方法中。