單體模式也有人稱爲單例模式,是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
方法有不少,具體看那種最適合。
考慮否嚴格的只須要一個實例對象的類,那麼就要考慮使用單體模式。使用數據緩存來存儲該單例,用做判斷單例是否已經生成,是單例模式主要的實現思路。