单例模式
保证一个类仅有一个实例,并提供给一个访问它的全局访问点。
1. 实现简单的单例模式
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 a1 = Singleton.getInstance('a1');var a2 = Singleton.getInstance('a2');
a1 === a2 // true;或者直接用闭包
var Singleton = function(name){ this.name = name;}Singleton.prototype.getName = function(){ altert(this.name);}Singleton.getInstance = (function(){ var instance = null; return function(name){ if(!instance){ instance = new Singleton(name); } return this.instance; }})();这种方式相对简单,但是增加了这个类的“不透明性”,这个类的使用者必须知道这个是一个单例,与之前的new一个类不同,使用者要用getinstance来获取对象
2.透明的单例模式
目标:实现一个“透明的单例类”,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。
下面例子用CreateDiv单例类,负责在页面中创建唯一的div节点
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 a1 = new CreateDiv('a1');var a2 = new CreateDiv('a2');a1 === a2 // true;虽然透明了,但有缺点,为了把instance封起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,增加了复杂度。
假如某天我们需要利用这个类,再页面中创建许多div,则就要让这个单例类变成一个普通的可产生多个实例的类,那我们必须得改写CreateDiv构造函数,把控制创建唯一对象的那段去掉。
3.用代理实现
由上可以知道,要想使得这个单例类变成一个普通的可以产生多个实例的类,那我们必须得改。
这时候我们把负责管理单例的代码移除出去,使他成为一个普通的创建div的类:
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);}接下来引入代理类ProxySingletonCreateDiv
var ProxySingletonCreateDiv = (function(){ var instance; return function(html){ if(!instance){ instance = new CreateDiv(html); } return instance; }})();var a1 = new ProxySingletonCreateDiv('a1');var a2 = new ProxySingletonCreateDiv('a2');a1 === a2; // true;这两个连起来就就可以就可以实现单例模式的实现,通过代理类加上一个CreateDiv普通类,组合
4. js中的单例模式
js是一门无类语言。传统的单例模式实现再js中并不适用
单例模式的核心是确保只有一个实例,并提供全局访问
全局变量不是单例模式,单再js开发中,我们经常会把全局变量当成单例来使用
var a ={};但是这样变量容易被覆盖。
因此需要降低全局变量命名的污染。
4.1 使用命名空间
var namespace = { a:function(){ ... }, b:function(){ ... }}4.2 使用闭包封装私有变量
这种方法把一些变量封装再闭包内部,只暴露接口与外界通信
var user = (function(){ var _name = 'seven',_age = 29; return { getUserInfo:function(){ return _name + '-' + _age; } }})();我们用下划线约定_name 和 _age。
5.惰性单例
惰性单例指的是需要的时候才去创建的对象实例。
例如:我们需要点击一个按钮显示一个弹窗,这个浮窗再这个页面里头是唯一的,不可能同时出现两个
**方法一:**在页面加载之后就去创建,然后点击btn把他显示出来
<html> <body> <button id="loginBtn"> 登录 </button> </body> <script> var loginLayer = (function(){ var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; })(); document.getElementById('loginBtn').onclick = function(){ loginLayer.style.display = 'block' } </script></html>,但是如果有时候不需要就不需要浪费空间去创建节点,需要改进
现在是点击的时候才会去创建节点
<html> <body> <button id="loginBtn"> 登录 </button> </body> <script> var loginLayer = function(){ var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; }; document.getElementById('loginBtn').onclick = function(){ var loginLayer = createLoginLayer(); loginLayer.style.display = 'block' } </script></html>但是虽然实现了惰性,这样就失去了单例的效果,频繁的增删不太合适。
此时可以用一个变量判断是否已经创建过浮窗了。
var createLoginLayer = (function(){ var div; return function(){ if(!div){ var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); } return div; }})()document.getElementById('loginBtn').onclick = function(){ var loginLayer = createLoginLayer(); loginLayer.style.display = 'block'}但是这个却违反了单一职责原则,这里创建和管理单例的逻辑全部都放在了一个func里头,但下次如果要创建iframe或者其他的东西,那就只能照抄代码了
因此把不变的部分分出来,把管理单例的逻辑抽出来,之后传创建的逻辑就行了。
var getSingle = function(fn){ var result ; return function(){ return result || result = fn.apply(this,arguments); }}接下来就可以用于创建弹窗的方法用于参数fn的形式传入getSingle,创建啥都行,用result保存fn的己算结果。result身在闭包中,永远不会销毁,之后如果result有值,直接返回
var createLoginLayer = function(){ var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; 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'}// 创建ifame啥的也是一样