單例模式javascript
在JavaScript中,單例(Singleton)模式是最基本又最有用的模式之一。這種模式提供了一種將代碼組織爲一個邏輯單元的手段,這個邏輯單元中的代碼能夠經過單一的變量進行訪問。確保單例對象只有一份實例,你就能夠確信本身的全部代碼使用的都是一樣的全局資源。html
單例類在JavaScript中用途普遍:java
(1)能夠用來劃分命名空間,以減小網頁中全局變量的數量;程序員
(2)能夠在一種名爲分支的技術中用來封裝瀏覽器之間的差別;數組
(3)能夠藉助於單例模式,將代碼組織得更爲一致,從而使代碼更容易閱讀和維護。瀏覽器
單例的基本結構安全
最基本的單例其實是一個對象字面值,它將一批有必定關聯的方法和屬性組織在一塊兒。例如以下JavaScript代碼:閉包
var Singleton = { attribute1: true; attribute2: 10 method1: function() { }, method2: function() { } };
這些成員能夠經過Singleton加圓點運算符來訪問:異步
Singleton.attribute1 = false; var total = Singleton. attribute2 + 5; var result = Singleton.method1();
對象字面值只是用以建立單例的方法之一,後面介紹的那些方法所建立的單體看起來更像其餘面嚮對象語言中的單例類。另外,並不是全部對象字面值都是單體,若是它只是用來模仿關聯數組或容納數據的話,那顯然不是單例。但若是它是用來組織一批相關方法和屬性的話,那就多是單例,其區別主要在於設計者的意圖。函數
建立擁有私有成員的單例
使用下劃線表示法
在單例對象內建立類的私有成員的最簡單、最直截了當的方法是使用下劃線表示法(在JavaScript業界,若是變量和方法是使用下劃線,則表示該變量和方法是私有方法,只容許內部調用,第三方不該該去調用)。參考實例以下:
GiantCorp.DataParser = { // 私有方法 _stripWhitespace: function(str) { return str.replace(/\s+/, ''); }, _stringSplit: function(str, delimiter) { return str.split(delimiter); }, // 公用方法 stringToArray: function(str, delimiter, stripWS) { if (stripWS) { str = this._stripWhitespace(str); } var outputArray = this._stringSplit(str, delimiter); return outputArray; } };
在如上代碼中,stringToArray方法中使用this訪問單體中的其餘方法,這是訪問單體中其餘成員或方法的最簡便的方法。但這樣作有一點風險,由於this並不必定指向GiantCorp.DataParser例如,若是把某個方法用做事件監聽器,那麼其中的this指向的是window對象,所以大多數JavaScript庫都會爲事件關聯進行做用域校訂,例如在這裏使用GiantCorp.DataParser比使用this更爲安全。
使用閉包
在單例對象中建立私有成員的第二種方法是藉助閉包。由於單例只會被實例化一次,因此沒必要擔憂本身在構造函數中聲明瞭多少成員。每一個方法和屬性都只會被建立一次,因此能夠把它們聲明在構造函數內部(所以也就位於同一個閉包中)。
使用閉包建立擁有私有成員的單例類的實例以下:
MyNamespace.Ssingleton = (function() { // 私有成員 var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { } function privateMethod2() { } return { // 公有成員 publicAttribute1: true; publicAttribute2: 10, publicMethod1: function() { }, publicMethod2: function() { } }; })();
這種單例模式又稱爲模塊模式,指的是它能夠把一批相關方法和屬性組織爲模塊並起到劃分命名空間的做用。 使用該種方式改造上面的實例,參考代碼以下:
GiantCorp.DataParser = (function() { var whiteSpaceRegex = /\s+/; // 私有方法 function stripWhitespace(str) { return str.replace(whiteSpaceRegex, ''); } function stringSplit(str, delimiter) { return str.split(delimiter); }, return { // 公用方法 stringToArray: function(str, delimiter, stripWS) { if (stripWS) { str = stripWhitespace(str); } var outputArray = stringSplit(str, delimiter); return outputArray; } }; })();
將私有成員放在閉包中能夠確保其不會在單例對象以外被使用,所以開發人員能夠自由的改變對象的實現細節,而不會殃及別人的代碼。還可使用這種辦法對數據進行保護和封裝。
在JavaScript中實現「懶漢式」單例模式
在如上的代碼中,單例對象都是在腳本加載時被建立出來。對於資源密集型或配置開銷甚大的單例,更合理的是使用「懶漢式」單例實現。這種實現方式的特別之處在於,對它們的訪問必須藉助於一個靜態方法,例如調用單例類的getInstance()方法得到對象實例。
參考實現代碼以下:
MyNamespace.Singleton = (function() { // 定義一個私有的屬性,該屬性用於儲存單例對象 var uniqueInstance; function constructor() { // 將單態操做放在這裏 } return { getInstance: function() { if (!uniqueInstance) { uniqueInstance = constructor(); } return uniqueInstance; } } })();
將一個單例轉換爲懶漢式加載方式後,必須對調用它的代碼進行修改,例如以前調用:
MyNamespace.Singleton.publicMethod1();
應該改爲以下代碼:
MyNamespace.Singleton.getInstance().publicMethod1();
若是以爲命名空間名稱太長,能夠建立一個別名來簡化它。
使用單例模式實現分支
分支是一種用來把瀏覽器之間的差別封裝到運行期間進行設置的動態方法中的技術。例如,假設咱們須要一個建立XHR對象的方法,這種XHR對象在大多數瀏覽器中是XMLHttpRequest對象的實例,但在IE早期版本中是某種ActiveX類的實例,這樣一種方法一般會進行某種瀏覽器嗅探或對象探測。若是不用分支技術,那麼每次調用這個方法時,全部這些瀏覽器嗅探代碼都要再次運行。若是該方法調用頻繁,將會嚴重影響效率。
要實現獲取不一樣瀏覽器的XHR對象的功能,參考實現代碼的實現步驟以下:
(1)判斷有多少個分支(有3個),這些分支按其返回的XHR對象類型命名,這三個分支都有一個名爲createXhrObject()的方法,該方法返回一個能夠執行異步請求的新對象;
(2)根據條件將3個分支中某一分支的對象賦給那個變量,具體作法是逐一嘗試XHR對象,直到遇到一個當前JavaScript環境所支持的對象爲止。
參考代碼以下所示:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>使用單例模式實現JavaScript中的分支</title> <script type="text/javascript"> var SimpleXhrFactory = (function() { // 三個分支 var standard = { createXhrObject: function() { alert("standard createXhrObject"); return new XMLHttpRequest(); } }; var activeXNew = { createXhrObject: function() { alert("activeXNew createXhrObject"); return new ActiveXObject("Msxml2.XMLHTTP"); } }; var activeXOld = { createXhrObject: function() { alert("activeXOld createXhrObject"); return new ActiveXObject("Microsoft.XMLHTTP"); } }; // 爲分支賦值 var testObject; try { testObject = standard.createXhrObject(); return standard; } catch(e) { try { testObject = activeXNew.createXhrObject(); return activeXNew; } catch(e) { try { testObject = activeXOld.createXhrObject(); return activeXOld; } catch(e) { throw new Error("在該瀏覽器環境中沒有發現XHR對象."); } } } })(); SimpleXhrFactory.createXhrObject(); </script></head><body></body>
用了分支技術後,全部的那些特性嗅探代碼都只會執行一次,而不是沒生成一個對象執行一次。這種技術適用於任何只有在運行時才能肯定具體實現的狀況。
單例模式的優缺點
在JavaScript中使用單例模式的主要優勢以下:
(1)對代碼的組織做用:它將相關方法和屬性組織在一個不會被屢次實例話的單例中,可使代碼的調試和維護變得更輕鬆。描述性的命名空間還能夠加強代碼的自我說明性。將方法包裹在單例中,能夠防止它們被其它程序員誤改。
(2)單例模式的一些高級變體能夠在開發週期的後期用於對腳本進行優化。
主要缺點以下:
(1)由於提供的是一種單點訪問,因此它有可能致使模塊間的強耦合。單體最好是留給定義命名空間和實現分支型方法這些用途,這些狀況下,耦合不是什麼問題;
(2)有時某些更高級的模式會更符合任務的須要。與「懶漢式」加載單例相比,虛擬代理能給予你對實例化方式更多的控制權。也能夠用一個真正的對象工廠來取代分支型單例。
本文學習地址:http://www.108js.com/article/article5/50021.html?id=333