JavaScript實(shí)現(xiàn)設(shè)計(jì)模式中的單例模式的一些技巧總結(jié)
一、使用全局變量保存單例
這是最簡(jiǎn)單的實(shí)現(xiàn)方法
function Person(){ this.createTime=new Date(); } var instance=new Person(); function getInstance(){ return instance; }
加載該js時(shí)就創(chuàng)建一個(gè)Person對(duì)象,保存到instance全局變量中,每次使用都取這個(gè)對(duì)象。如果一次都沒使用,那么創(chuàng)建的這個(gè)對(duì)象則浪費(fèi)了,我們可以優(yōu)化一下,
var instance function getInstance(){ if(!instance){ instance=new Person(); } return instance; }
這樣,第一次使用時(shí)才創(chuàng)建對(duì)象。
這個(gè)方法的缺點(diǎn)是,instance是全局的變量,在多人合作或者開發(fā)周期比較長(zhǎng)的情況下,很難保證instance不會(huì)被其它代碼修改或覆蓋,很可能到調(diào)用的時(shí)候,發(fā)現(xiàn)instance根本就不是Person對(duì)象。
我們考慮下使用閉包來封裝起instance,使它不再是全局變量就可以解決這個(gè)問題了
二、閉包創(chuàng)建對(duì)象
var getInstance(){ var instance; return function(){ if(!instance){ instance=new Person(); } return instance; } }();
這樣,instance就被封裝起來了,不用擔(dān)心被修改了。
現(xiàn)在通過getInstance()函數(shù)可以獲得單例了。新的問題,如果我通過new Person()來創(chuàng)建對(duì)象,獲得的還是多個(gè)對(duì)象,javascript又不可以像java一樣把構(gòu)造器私有化。那怎么樣可以讓多次new出來的對(duì)象都是一個(gè)實(shí)例呢?
三、構(gòu)造函數(shù)的靜態(tài)屬性緩存實(shí)例
先看代碼
function Person(){ //如果已經(jīng)緩存了實(shí)例,則直接返回緩存的實(shí)例 if(typeof Person.instance==='object'){ return Person.instance; } this.createTime=new Date(); //緩存實(shí)例 Person.instance=this; return this; }
從代碼可以看到,第一次new時(shí),if的條件返回false,會(huì)往下走,初始化對(duì)象,然后保存對(duì)象到Person.instance這個(gè)靜態(tài)屬性中。
第二次new 時(shí),if的條件返回true,直接返回Person.instance,不會(huì)再往下運(yùn)行初始化的代碼。所以不管new幾次,返回的都是第一次創(chuàng)建的對(duì)象。
這個(gè)方法的缺點(diǎn)和方法一的缺點(diǎn)一樣,Person.instance也是公開屬性,有可能會(huì)被修改。
我們參考方法二,使用閉包來封裝一個(gè),也許就能解決該問題了
四、重寫構(gòu)造函數(shù)
這個(gè)方法要使用閉包,但不能像方法二那么簡(jiǎn)單,我們需要重寫構(gòu)造函數(shù)。
function Person(){ //緩存實(shí)例 var instance=this; this.createTime=new Date(); //重寫構(gòu)造函數(shù) Person=function(){ return instance; } }
第一次new 時(shí),調(diào)用原始構(gòu)造函數(shù)先緩存該實(shí)例,然后再初始化,同時(shí),重寫該構(gòu)造函數(shù)。以后再new 時(shí),永遠(yuǎn)調(diào)用不到原始的構(gòu)造函數(shù)了,只能調(diào)用到重寫后的構(gòu)造函數(shù),而這個(gè)函數(shù)總是返回緩存的instance.
上面的方法似乎沒什么問題,但通過下面的測(cè)試,可以發(fā)現(xiàn)問題
//向原型添加屬性 Person.prototype.prop1=true; var p1=new Person(); //在創(chuàng)建初始化對(duì)象后,再次向該原型添加屬性 Person.prototype.prop2=true; var p2=new Person(); //開始測(cè)試 console.log(p1.prop1);//結(jié)果為true console.log(p2.prop1);//結(jié)果為true console.log(p1.prop2);//結(jié)果為undefined console.log(p2.prop2);//結(jié)果為undefined console.log(p1.constructor===Person);//結(jié)果為false console.log(p2.constructor===Person);//結(jié)果為false
我們預(yù)期中的結(jié)果,應(yīng)該是全都是true。
分析一下上述測(cè)試代碼
Person.prototype.prop1=true;是在原始構(gòu)造函數(shù)的原型下增加了prop1這個(gè)屬性,并賦值
而在執(zhí)行 var p1=new Person();之后,Person這個(gè)構(gòu)造函數(shù)已經(jīng)被重寫了
所以Person.prototype.prop2=true;是在新的原型下增加prop2這個(gè)屬性
var p2=new Person(); p2和p1實(shí)際上是同一個(gè)對(duì)象,即原始構(gòu)造函數(shù)創(chuàng)建的對(duì)象
所以p1 p2都有prop1這個(gè)屬性,而沒有prop2這個(gè)屬性
同樣的,p1 p2的constructor指向的也是原始的構(gòu)造函數(shù),而Person此時(shí)已不是原來那個(gè)函數(shù)了
為了能按預(yù)期的結(jié)果那樣運(yùn)行,可以通過一些修改來實(shí)現(xiàn)
function Person(){ //緩存實(shí)例 var instance=this; //重寫構(gòu)造函數(shù) Person=function(){ return instance; } //保留原型屬性 Person.prototype=this; //實(shí)例 instance=new Person(); //重置構(gòu)造函數(shù)引用 instance.constructor=Person; //其他初始化 instance.createTime=new Date(); return instance; }
再運(yùn)行前面的測(cè)試代碼,結(jié)果都是true了。
五、惰性加載:
在大型或復(fù)雜的項(xiàng)目中,起到了優(yōu)化的作用:那些開銷較大卻很少用到的組件可以被包裝到惰性加載單例中,示例程序:
/* 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; } } })();
六、使用分支單例:
針對(duì)特定環(huán)境的代碼可以被包裝到分支型單例中,示例程序:
/* 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.'); } } } })();
- JavaScript設(shè)計(jì)模式之單例模式實(shí)例
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式(單例模式)
- JavaScript設(shè)計(jì)模式之單例模式詳解
- js設(shè)計(jì)模式之單例模式原理與用法詳解
- JS 設(shè)計(jì)模式之:?jiǎn)卫J蕉x與實(shí)現(xiàn)方法淺析
- javascript設(shè)計(jì)模式 – 單例模式原理與應(yīng)用實(shí)例分析
- JavaScript 設(shè)計(jì)模式 安全沙箱模式
- JavaScript設(shè)計(jì)模式之觀察者模式(發(fā)布者-訂閱者模式)
- JavaScript 設(shè)計(jì)模式之組合模式解析
- javascript設(shè)計(jì)模式之解釋器模式詳解
- 常用的Javascript設(shè)計(jì)模式小結(jié)
- JavaScript設(shè)計(jì)模式---單例模式詳解【四種基本形式】
相關(guān)文章
js動(dòng)態(tài)添加事件并可傳參數(shù)示例代碼
js動(dòng)態(tài)添加事件可以搜索到很多的相關(guān)文章,不過可以傳參數(shù)的就沒有幾個(gè)了,下面有個(gè)不錯(cuò)的示例可以滿足大家對(duì)傳參的需求,感興趣的各位不要錯(cuò)過2013-10-10js復(fù)制文本到粘貼板(Clipboard.writeText())
這篇文章主要介紹了js復(fù)制文本到粘貼板(Clipboard.writeText()),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07canvas壓縮圖片轉(zhuǎn)換成base64格式輸出文件流
本文主要介紹了canvas壓縮圖片轉(zhuǎn)換成base64格式輸出文件流的方法,具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03Javascript實(shí)現(xiàn)頁面跳轉(zhuǎn)的幾種方式分享
這篇文章介紹了Javascript實(shí)現(xiàn)頁面跳轉(zhuǎn)的幾種方式,有需要的朋友可以參考一下2013-10-10BootStrap Validator 根據(jù)條件在JS中添加或移除校驗(yàn)操作
這篇文章主要介紹了BootStrap Validator 根據(jù)條件在JS中添加或移除校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2017-10-10教你如何使用firebug調(diào)試功能了解javascript閉包和this
這篇文章主要介紹了教你如何使用firebug調(diào)試功能了解javascript閉包和this,javascript的調(diào)試也是一個(gè)比較大的難點(diǎn),很多基礎(chǔ)的東西都需要自己去摸索,這里將自己的經(jīng)驗(yàn)分享給大家,希望對(duì)大家能夠有所幫助2015-03-03JS組件Bootstrap實(shí)現(xiàn)圖片輪播效果
這篇文章主要為大家詳細(xì)介紹了JS組件Bootstrap實(shí)現(xiàn)圖片輪播效果的具體代碼,對(duì)圖片輪播效果感興趣的小伙伴們可以參考一下2016-05-05使用原生JS實(shí)現(xiàn)滾輪翻頁效果的示例代碼
這篇文章主要介紹了使用原生JS實(shí)現(xiàn)滾輪翻頁效果的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05