JavaScript设计模式笔记-单例模式

定义

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局放完电

4.1 实现单例模式

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

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 
} 
var a=Singleton.getInstance('sven1'); 
var b=Singleton.getInstance('sven2'); 
console.log(a===b)//true 

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

首先创建了一个构造器也就是Singleton其中this.instance是用来判断是否已经构造过一次类,当执行var a=Singleton.getInstance(‘sven1’)的时候 会构造一个类. 然后执行 var b的时候 因为已经构造过 仅仅是返回this.instance而不去重新构造.这就是单例模式.但是上述单例模式有个问题 不能直接通过new xxx来构造一个单例模式 而必须通过 xxx.func来构造.

4.2 透明的单例模式

比如一个创建createDiv的函数 当创建单例模式只需要new xxx 而不是去xxx.func来构造

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'); 
console.log(a===b);//true 

这段代码中 createDiv函数实际上负责了两件事,第一件事是创建对象和执行初始化init方法,第二个是保证只有一个对象. 但是这样看起来有点奇怪 并且如果后期需要把单例模式改写为多个实例的类 那么必须的改写createDiv构造函数

4.3 用代理实现代理模式

通过引入代理类的方式来解决上述问题

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); 
} 
//接下来引入代理类proxSingletonCreateDiv 
var proxSingletonCreateDiv=function(){ 
   var instance; 
   return function(html){ 
       if(!instance){ 
           instance=new CreateDiv(html) 
       } 
       return instance 
   } 
}(); 
var a=new proxSingletonCreateDiv('sven1'); 
var b=new proxSingletonCreateDiv('sven2'); 
console.log(a===b);//true 

这样createDiv只需要负责一件事也就是单一原则. 当函数被显式的返回一个对象 也就是proxSingletonCreateDiv显式的返回了instance 所以最终 a 与 b 返回的是CreateDiv

4.4 JavaScript中的单例模式

JavaScript原本就是一门无class语言,单例模式的核心就是 确保只有一个实例,并且提供全局访问.那么很简单 JavaScript的全局变量就可以实现.

var a={} 

但是全局变量会造成命名空间污染 而且很容易被覆盖.那么我们可以使用以下的几种方法来降低全局变量带来的命名污染

1.使用命名空间

var namespace1={ 
   a:function(){ 
       console.log(1); 
   }, 
   b:function(){ 
       console.log(2); 
   } 
}; 

把a和b都定义为namespace1的属性 这样就可以减少变量与全局中用于打交道的机会,而且我们还可以动态创建命名空间

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' ); 
   console.dir( MyApp ); 
   // 上述代码等价于: 
   var MyApp = { 
       event: {}, 
       dom: { 
           style: {} 
       } 
}; 

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

这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信

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

4.5 惰性单例

如果我们全局都用不到这个单例模式.但是我们初始化又加载了这个单例模式 这就会造成一个内存开销.在JavaScript中基于类的单例模式并不适用 那么下面来介绍与全局变量结合实现惰性的单例.

var loginLayer=(function(){ 
   var div=document.createElement('div'); 
   div.innerHtml='我是登陆悬浮窗'; 
   div.style.display='none'; 
   document.body.appendChild(div); 
   return div 
})() 
document.getElementById('loginBtn').onclick=funciton(){ 
   loginLayer.style.display='none' 
} 

这个单例在初始化就被创建了,如果我们仅仅是进去浏览并不登陆.这个单例就被浪费了.下面来写惰性加载的方法

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=funciton(){ 
   var loginLayer=createLoginLayer() 
   loginLayer.style.display='none' 
} 

这样就做到了惰性用需加载 只有需要的时候才加载惰性单例

4.6 通用的惰性单例

但是上述的代码仍旧违反了一个原则 也就是单一职责的原则 创建对象和管理单例的逻辑都放在createLoginLayer里面.如果下次我们需要createScript得如法炮制一遍.那么我们可以把不变的抽出来也就是引入一个代理.

var getSingle=function(fn){ 
   var Single; 
   return function(){ 
       return Single||(Single=fn.apply(this,arguments)) 
   } 
} 
   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(); 
       console.log(216,loginLayer) 
       loginLayer.style.display = 'block'; 
   }; 

   //下面我们再试试创建唯一的iframe 用于动态加载第三方页面: 
   var createSingleIframe = getSingle( function(){ 
       var iframe = document.createElement ( 'iframe' ); 
       document.body.appendChild( iframe ); 
       return iframe; 
   }); 
   document.getElementById( 'loginBtn' ).onclick = function(){ 
       var loginLayer = createSingleIframe(); 
       console.log(227,loginLayer) 
       loginLayer.src = 'http://baidu.com'; 
   }; 

当调用getSingle函数的时候 他会返回一个闭包 并且会检测是否存在了单例 如果存在就返回存在的单例 否则则创建单例.比如jquery的one只需要绑定一次click事件

var bindEvent = function(){ 
   $( 'div' ).one( 'click', function(){ 
       alert ( 'click' ); 
   }); 
}; 
var render = function(){ 
   console.log( '开始渲染列表' ); 
   bindEvent(); 
}; 
render(); 

render(); 
render(); 

用上述的单例模式也可以实现

var bindEvent = getSingle(function(){ 
   document.getElementById( 'div1' ).onclick = function(){ 
       alert ( 'click' ); 
   } 
   return true; 
}); 
var render = function(){ 
   console.log( '开始渲染列表' ); 
   bindEvent(); 
}; 
render(); 
render(); 
render(); 

小结论闭包和高阶函数的重要性.单例模式最重要的在于 创建对象和管理单例的职责被分布在两个不同的方法中 这才是单例模式的威力。