单体模式也有人称为单例模式,是javaScript 中最基本但又最有用的模式之一。java
一讲到概念第一反应就是WTF设计模式
其实单体模式是最经常使用,也是最有用的一种模式。而咱们也会在项目中不知不觉的写一些单体模式,只是咱们没有意识到,这也是一种设计模式。看一个简单的例子:缓存
var zoom = { bird: 10, monkey: 10, play: function() {}, eat: function() {} }
这和我随手找的一个对象有什么区别,从严格意义上讲这确实不能称为一个单体。但咱们能够分析一下这个对象,其中有两个zoom相关的属性,和两个zoom相关的方法。实际上最简单的单体就是字面量,它是把一些有必定关系的方法和属性组织在一块儿。安全
但他违背了面向对象设计的一条原则:类能够被扩展,但不该该被修改。听到这里咱们也不须要慌,由于(Python,Ruby)都容许在定义了类以后对其修改。闭包
可能你仍是不知道单体与普通字面量有什么不一样,那就再看一个例子:函数
var zoom = function() { var bird = 10; var monkey = 10; this.play = function() {} this.eat = function() {} } var zoom1 = new zoom(); var zoom2 = new zoom(); console.log(zoom1 === zoom2)//false;
zoom被实例化两次,但两个实例明显不相等。由于zoom1,zoom2的指针,并无只向内存中的同一地址。这也正好能够引出单体与不一样字面量的不一样。按传统定义,单体是一个只能被实例化一次,而且能够经过一个众所周知的访问点访问的对象。后半句简单说,就是能够全局访问。this
总结一下上面内容。单体是一个用来划分命名空间并将一批相关方法和属性组织在一块儿的对象,若是它能够被实例化,那么它只能被实例化一次。spa
若是咱们的变量都定义在全局中,一不当心别人从新用相同的变量名,定义一个变量,那咱们的代码就会出问题,并且这种问题很难排查。为了不这种不肯定性,咱们能够把代码组织在命名空间中(其实,就是另外一个对象中,但这个对象,更具备包含性)设计
var ZoomSpace = { otherFunction: function() {} } ZoomSpace.zoomUtil = { bird: 10, monkey: 10, play: function() {}, eat: function() {} }
这样若是在全局中重定义了zoomUtil,咱们的zoomUtil也不会受到影响。指针
function zoomUtil() { //若是被实例化过,就返回实例化的结果 if(zoomUtil.unique !== undefined) { return zoomUtil.unique; } var bird = 10; var monkey = 10; function play() {}; function eat(){}; return zoomUtil.unique = this; } var zoom1 = new zoomUtil(); var zoom2 = new zoomUtil(); console.log(zoom1 === zoom2)//true
这种方式比较简洁,经过判断是否已经实例化过,可是也没啥安全性,若是在外部修改zoomUtil.unique 那极可能单例模式就被破坏了。
下划线表示私有成员,是约定俗称的一种方法,告诉别人不要直接访问私有方法。
使用上面咱们已经定义好的命名空间:
ZoomSpace.zoomUtil = { bird: 10, monkey: 10, _runTime: function() { return ++this.bird; }, play: function() { //return this._runTime(); 尽可能不要再共有方法中使用this //this极可能由于此方法使用在别的函数中致使指向window return ZoomSpace.zoomUtil._runTime(); }, eat: function() {} } console.log(ZoomSpace.zoomUtil.play());//11
这种方法的安全性也很差,主要靠人为的约束,可是若是有人非要用咱们的私有方法,致使咱们删除或者修改私有方法后,他的程序不能用,只能能跟他说no zuo no die。
以前咱们的单体都是这样的:
ZoomSpace.zoomUitl = {};
如今咱们用一个当即执行的函数建立,像这样:
ZoomSpace.zoomUitl = (function(){ return {}; })();
以往的作法是把变量和函数定义在构造函数内部。所以,每次生成一个实例,全部的方法属性都会被再次建立。这样的作法很低效。而如今的作法咱们没必要要担忧定义的属性过多,由于它只会被实例化一次。咱们来改写上面的例子:
ZoomSpace.zoomUtil = (function(){ //私有属性和方法 var bird = 10; var monkey = 10; function runTime(){ return ++bird;//不须要使用this }; function otherFunction(){}; return { play:function(){ return runTime(); } } })() console.log(ZoomSpace.zoomUtil.play());//11
若是你想经过new的方式来写,能够像下面这样修改:
var ZoomSpace = {}; ZoomSpace.zoomUtil = (function() { //私有属性和方法 var unique; //惟一值 function zoomUtilConstructor() { // 是否被实例化过 if(unique.constructor === zoomUtilConstructor) { //若是实例化过,返回已经实例化的对象 return unique; } var bird = 10; var monkey = 10; function runTime() { return ++bird; }; this.play = function(){ return runTime(); } // 保存实例化的对象 return unique = this; } // 保存构造函数 return unique = zoomUtilConstructor; })() var zoom1 = new ZoomSpace.zoomUtil(); var zoom2 = new ZoomSpace.zoomUtil(); console.log(zoom1 === zoom2); //true
方法有不少,具体看那种最适合。
考虑否严格的只须要一个实例对象的类,那么就要考虑使用单体模式。使用数据缓存来存储该单例,用做判断单例是否已经生成,是单例模式主要的实现思路。