一、使用全局變量保存單例
這是最簡單的實現方法
function Person(){ this.createTime=new Date(); } var instance=new Person(); function getInstance(){ return instance; }
加載該js時就創建一個Person對象,保存到instance全局變量中,每次使用都取這個對象。如果一次都沒使用,那麼創建的這個對象則浪費了,我們可以優化一下,
var instance function getInstance(){ if(!instance){ instance=new Person(); } return instance; }
這樣,第一次使用時才創建對象。
這個方法的缺點是,instance是全局的變量,在多人合作或者開發周期比較長的情況下,很難保證instance不會被其它代碼修改或覆蓋,很可能到調用的時候,發現instance根本就不是Person對象。
我們考慮下使用閉包來封裝起instance,使它不再是全局變量就可以解決這個問題了
二、閉包創建對象
var getInstance(){ var instance; return function(){ if(!instance){ instance=new Person(); } return instance; } }();
這樣,instance就被封裝起來了,不用擔心被修改了。
現在通過getInstance()函數可以獲得單例了。新的問題,如果我通過new Person()來創建對象,獲得的還是多個對象,javascript又不可以像java一樣把構造器私有化。那怎麼樣可以讓多次new出來的對象都是一個實例呢?
三、構造函數的靜態屬性緩存實例
先看代碼
function Person(){ //如果已經緩存了實例,則直接返回緩存的實例 if(typeof Person.instance==='object'){ return Person.instance; } this.createTime=new Date(); //緩存實例 Person.instance=this; return this; }
從代碼可以看到,第一次new時,if的條件返回false,會往下走,初始化對象,然後保存對象到Person.instance這個靜態屬性中。
第二次new 時,if的條件返回true,直接返回Person.instance,不會再往下運行初始化的代碼。所以不管new幾次,返回的都是第一次創建的對象。
這個方法的缺點和方法一的缺點一樣,Person.instance也是公開屬性,有可能會被修改。
我們參考方法二,使用閉包來封裝一個,也許就能解決該問題了
四、重寫構造函數
這個方法要使用閉包,但不能像方法二那麼簡單,我們需要重寫構造函數。
function Person(){ //緩存實例 var instance=this; this.createTime=new Date(); //重寫構造函數 Person=function(){ return instance; } }
第一次new 時,調用原始構造函數先緩存該實例,然後再初始化,同時,重寫該構造函數。以後再new 時,永遠調用不到原始的構造函數了,只能調用到重寫後的構造函數,而這個函數總是返回緩存的instance.
上面的方法似乎沒什麼問題,但通過下面的測試,可以發現問題
//向原型添加屬性 Person.prototype.prop1=true; var p1=new Person(); //在創建初始化對象後,再次向該原型添加屬性 Person.prototype.prop2=true; var p2=new Person(); //開始測試 console.log(p1.prop1);//結果為true console.log(p2.prop1);//結果為true console.log(p1.prop2);//結果為undefined console.log(p2.prop2);//結果為undefined console.log(p1.constructor===Person);//結果為false console.log(p2.constructor===Person);//結果為false
我們預期中的結果,應該是全都是true。
分析一下上述測試代碼
Person.prototype.prop1=true;是在原始構造函數的原型下增加了prop1這個屬性,並賦值
而在執行 var p1=new Person();之後,Person這個構造函數已經被重寫了
所以Person.prototype.prop2=true;是在新的原型下增加prop2這個屬性
var p2=new Person(); p2和p1實際上是同一個對象,即原始構造函數創建的對象
所以p1 p2都有prop1這個屬性,而沒有prop2這個屬性
同樣的,p1 p2的constructor指向的也是原始的構造函數,而Person此時已不是原來那個函數了
為了能按預期的結果那樣運行,可以通過一些修改來實現
function Person(){ //緩存實例 var instance=this; //重寫構造函數 Person=function(){ return instance; } //保留原型屬性 Person.prototype=this; //實例 instance=new Person(); //重置構造函數引用 instance.constructor=Person; //其他初始化 instance.createTime=new Date(); return instance; }
再運行前面的測試代碼,結果都是true了。
五、惰性加載:
在大型或復雜的項目中,起到了優化的作用:那些開銷較大卻很少用到的組件可以被包裝到惰性加載單例中,示例程序:
/* Singleton with Private Members, step 3. */ MyNamespace.Singleton = (function() { // Private members. var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { ... } function privateMethod2(args) { ... } return { // Public members. publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } }; })(); /* General skeleton for a lazy loading singleton, step 1. */ MyNamespace.Singleton = (function() { function constructor() { // All of the normal singleton code goes here. // Private members. var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { ... } function privateMethod2(args) { ... } return { // Public members. publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } } } })(); /* General skeleton for a lazy loading singleton, step 2. */ MyNamespace.Singleton = (function() { function constructor() { // All of the normal singleton code goes here. ... } return { getInstance: function() { // Control code goes here. } } })(); /* General skeleton for a lazy loading singleton, step 3. */ MyNamespace.Singleton = (function() { var uniqueInstance; // Private attribute that holds the single instance. function constructor() { // All of the normal singleton code goes here. ... } return { getInstance: function() { if(!uniqueInstance) { // Instantiate only if the instance doesn't exist. uniqueInstance = constructor(); } return uniqueInstance; } } })();
六、使用分支單例:
針對特定環境的代碼可以被包裝到分支型單例中,示例程序:
/* SimpleXhrFactory singleton, step 1. */ var SimpleXhrFactory = (function() { // The three branches. var standard = { createXhrObject: function() { return new XMLHttpRequest(); } }; var activeXNew = { createXhrObject: function() { return new ActiveXObject('Msxml2.XMLHTTP'); } }; var activeXOld = { createXhrObject: function() { return new ActiveXObject('Microsoft.XMLHTTP'); } }; })(); /* SimpleXhrFactory singleton, step 2. */ var SimpleXhrFactory = (function() { // The three branches. var standard = { createXhrObject: function() { return new XMLHttpRequest(); } }; var activeXNew = { createXhrObject: function() { return new ActiveXObject('Msxml2.XMLHTTP'); } }; var activeXOld = { createXhrObject: function() { return new ActiveXObject('Microsoft.XMLHTTP'); } }; // To assign the branch, try each method; return whatever doesn't fail. var testObject; try { testObject = standard.createXhrObject(); return standard; // Return this if no error was thrown. } catch(e) { try { testObject = activeXNew.createXhrObject(); return activeXNew; // Return this if no error was thrown. } catch(e) { try { testObject = activeXOld.createXhrObject(); return activeXOld; // Return this if no error was thrown. } catch(e) { throw new Error('No XHR object found in this environment.'); } } } })();