singleton模式,又名单例模式。顾名思义,就是只能实例化一次的类(javascript中没有真正的类,我们通常用函数来模拟类,习惯称之为"伪类")。具体地说,singleton模式,就是在该实例不存在的情况下,可以通过可以方法创建一个类来实现创建类的新实例;如果实例已经存在,它会返回一个该对象的引用。
接下来我将用一个案列来将singleton模式进行分析。在我们的web网上通常会看到一些图片的放大查看,如下图:
当我的鼠标碰到图中的帅哥时,它会出现一个放大的图标(这里放大图标有点奇怪= = !),于是我们点击后会出现这样的画面如下图:
没错,放大后,就可以看到这个美男子的芳容了。这就是我给大家带来的例子,现在我们对所用到的js进行分析:(html,css部分忽略)
首先我们编写兼容各版本浏览器的事件函数(参考javascript高级程序设计的事件部分):这个示例中只会用到EventUtil中的adds函数。
var EventUtil = { adds : function(element,type,func){ if(element.addEventListener){ element.addEventListener(type,func,false); }else if(element.attachEvent){ element.attachEvent("on"+type,func); }else{ element["on"+type] = func; } }, removes : function(element,type,func){ if(element.removeEventListener){ element.removeEventListener(type,func,false); }else if(element.detachEvent){ element.detachEvent("on"+type,func); }else{ element["on"+type] = null; } }, getEvent : function(event){ return event ? event : window.event; }, getTarget : function(event){ return event.target || event.srcElement; }, preventDefault : function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation : function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } }
} function $(id){ return typeof id === "string" ? document.getElementById(id) : id; }
接下来开始设计这个放大的功能。首先你会想到既然我要放大,那我肯定要有放大的图片以及放大图片所盛放的容器。没错,我们创建这些必定会用到一系列的Dom方法,这个稍后会见到。
当我们每次放大图片的时候有一个地方是不会变的,就是背景阴影和盛放图片的容器。每次都是一个背景的div和一个盛放图片的div对象在我们的浏览器中显示,然后关闭后显示。具体分析就是这个对象可以是唯一的,我只需要将它保存在缓存中,放大的时候将这个对象通过document.body.appendChild()方法将它载入到我们的页面,关闭的时候再使用removeChild()方法将它从页面移除(并不是从缓存移除),那我们如何保证每次调用这个对象时都是第一次调用时的对象呢?(这里说的可能有点复杂,简洁说就是每次都是调用最初的那个对象)。
这个时候singleton模式就可以很好的解决这个问题。回到刚刚所有的保存在缓存中,我们都很熟悉js等高级语言的垃圾回收机制,它采用引用计数的方式(参考javascript高级程序设计第四章)来进行内存管理。既然我们需要这个对象始终保持唯一,那么就需要将这个对象始终保存在缓存,也就是说不然它被回收,那我们应该怎么做到呢?
闭包。没错,我们可以采用闭包(singleton模式的建立依赖闭包,这个概念就不提了)。于是我们可以采取如下的方法实现:
var showBig = (function(){ var listen; //作为对象的引用 function initial(){ var listen_2,listen_3; function createBgShadow(){ //先不考虑这里的代码 } function createBgCenterDiv(){ //先不考虑这里的代码 } return{ create : function(){ //先不考虑这里的代码 }, removes : function(){ //先不考虑这里的代码 } } } return{ getShadow : function(){ if(!listen){ //如果listen没有引用这个对象,那么就调用一次initial方法。若引用了,则直接跳过 listen = initial(); } return listen; //返回这个对象(或者对象的引用) }, getPic : function(picUrl){ //先不考虑这里的代码 } } })();
上述代码中可以看到,为了保存我要保留在内存中的对象,我使用了一个Listen作为我的监听器(我个人称之为监听器,本意为这个对象的引用)。因为Listen在闭包环境中,当第一次返回Initial()方法后,listen始终带有initial()方法所返回的对象,当第二次或者以后再次调用时,listen并未从内存中消失,因此直接返回内存中的listen即可。
而我们要返回的是一个背景阴影对象,就可以这么做:
function createBgShadow(){ //阴影背景的创建 var shadowBg = document.createElement("div"); shadowBg.setAttribute("id","picShadow"); return shadowBg; } function createBgCenterDiv(){ //盛放图片的容器的创建 var shadowBgCenterDiv = document.createElement("div"); shadowBgCenterDiv.setAttribute("id","realDiv"); //放大之后的图片的创建 var shadowBgCenterPic = document.createElement("img"); shadowBgCenterPic.setAttribute("src",""); shadowBgCenterPic.setAttribute("id","realPic"); shadowBgCenterDiv.appendChild(shadowBgCenterPic); //取消放大的图片(那个×)的创建 var cancel = document.createElement("img"); cancel.setAttribute("id","cancelPic"); cancel.setAttribute("src","img/cancel.png"); shadowBgCenterDiv.appendChild(cancel); return shadowBgCenterDiv; } //注意它们都返回了一个DOM的节点对象。这一步很关键。
然后将这两个对象保存在Listen_2,listen_3中,同样他们也要和initial()一样,保存在内存中。于是我们可以这么做:
return{ //create()方法将节点加入到html页面 create : function(){ if(!listen_2 || !listen_3){ //listen2保存阴影背景的节点 listen_2 = createBgShadow(); //listen3保存图片以及图片容器的节点 listen_3 = createBgCenterDiv(); } //由于listen_2和Listen_3也使用闭包,始终保存在内存中,这里也只会创建一次对象 document.body.appendChild(listen_2); document.body.appendChild(listen_3); }, //removes()方法将节点移出html页面 removes : function(){ var picShadow = $("picShadow"); var realDiv = $("realDiv"); document.body.removeChild(picShadow); document.body.removeChild(realDiv); } }
这时候已经很明确了singleton模式的作用,它避免了多次创建重复的对象,将我们可以反复使用的对象只创建一次。我们用listen,listen_2,listen_3来保存了这些我们需要反复使用的对象。singleton模式利用闭包可以更快速的创建我们需要的应用,但是这些对象也会一直保存在我们的内存中,所以singleton模式是有它自己的适用范围的,重点突出一个"单"字。
简单的总结一下singleton模式:
1.singleton模式是javascript最基本,最有用的模式之一,它提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码通过单一的变量进行访问。(通过单一的接口进行访问,这里是第一个单例中的create()接口和第二个单例中的getShadow()接口)
2.singleton模式可以用来划分命名空间,以减少全局变量的泛滥。(上述我们只是用了一个showBig的全局变量)
3.singleton模式通过js的闭包特性反复引用内存中的同一对象,使运用程序更快速的执行。
4.singleton模式的弊端在于提供的是一种单点访问,可能导致模块间的强耦合(我们的程序遵守高内聚低耦合的原则)。在选择这种模式的时候应该考虑到这种情况。并不是所有情况都要选择它,而是先分析好。
完整代码如下(测试时不要忘了修改图片的url):
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <style type="text/css"> *{margin:0px;padding:0px;} div.singleton{177px;height:110px;margin:100px auto;position:relative;} img{177px;height:110px;} img.real{display:block;} img.bigger{position:absolute;top:0px;left:0px;opacity:0} img.bigger:hover{opacity:1;cursor:pointer;z-index:100;} #picShadow{background:#555;position:fixed;top:0px;left:0px;100%;height:100%;filter:alpha(opacity=70);opacity:0.7;-moz-opacity:0.7;z-index:100;} #realDiv{position:fixed;top:120px;left:283px;display:block;800px;height:400px;z-index:200;} #realPic{display:block;auto;height:auto;max-780px;max-height:380px;margin:0 auto;padding:5px;background-color:#fff;} #cancelPic{display:block;auto;height:auto;margin:0 auto;cursor:pointer;} </style> </head> <body> <div class="singleton"> <img class="real" id="real" src="img/cjy.jpg"/> <img class="bigger" id="bigger" src="img/bigger.png"/> </div> </body> <script type="text/javascript"> /* 兼容事件对象 */ var EventUtil = { adds : function(element,type,func){ if(element.addEventListener){ element.addEventListener(type,func,false); }else if(element.attachEvent){ element.attachEvent("on"+type,func); }else{ element["on"+type] = func; } }, removes : function(element,type,func){ if(element.removeEventListener){ element.removeEventListener(type,func,false); }else if(element.detachEvent){ element.detachEvent("on"+type,func); }else{ element["on"+type] = null; } }, getEvent : function(event){ return event ? event : window.event; }, getTarget : function(event){ return event.target || event.srcElement; }, preventDefault : function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation : function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } } function $(id){ return typeof id === "string" ? document.getElementById(id) : id; } //单例 var showBig = (function(){ var listen; function initial(){ var listen_2,listen_3; function createBgShadow(){ var shadowBg = document.createElement("div"); shadowBg.setAttribute("id","picShadow"); return shadowBg; } function createBgCenterDiv(){ var shadowBgCenterDiv = document.createElement("div"); shadowBgCenterDiv.setAttribute("id","realDiv"); //放大图片 var shadowBgCenterPic = document.createElement("img"); shadowBgCenterPic.setAttribute("src",""); shadowBgCenterPic.setAttribute("id","realPic"); shadowBgCenterDiv.appendChild(shadowBgCenterPic); //创建取消放大的图片 var cancel = document.createElement("img"); cancel.setAttribute("id","cancelPic"); cancel.setAttribute("src","img/cancel.png"); shadowBgCenterDiv.appendChild(cancel); return shadowBgCenterDiv; } return{ create : function(){ if(!listen_2 || !listen_3){ listen_2 = createBgShadow(); listen_3 = createBgCenterDiv(); } document.body.appendChild(listen_2); document.body.appendChild(listen_3); }, removes : function(){ var picShadow = $("picShadow"); var realDiv = $("realDiv"); document.body.removeChild(picShadow); document.body.removeChild(realDiv); } } } return{ getShadow : function(){ if(!listen){ listen = initial(); alert("1"); } return listen; }, getPic : function(picUrl){ var realPic = $("realPic"); realPic.setAttribute("src",picUrl); } } })(); //事件处理 //为方便这里不做平稳退化 var bigger = $("bigger"); var gS_1 = showBig; EventUtil.adds(bigger,"click",function(event){ gS_1.getShadow().create(); gS_1.getPic($("real").src); //这里做一个关闭检测 if($("cancelPic")){ var cancelPic = $("cancelPic"); EventUtil.adds(cancelPic,"click",function(event){ gS_1.getShadow().removes(); }); } }); </script> </html>