這篇文章主要介紹“JavaScript閉包以及幾種設(shè)計(jì)模式的詳細(xì)介紹”,在日常操作中,相信很多人在JavaScript閉包以及幾種設(shè)計(jì)模式的詳細(xì)介紹問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”JavaScript閉包以及幾種設(shè)計(jì)模式的詳細(xì)介紹”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
創(chuàng)新互聯(lián)公司主要從事成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)呼中,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專(zhuān)業(yè),歡迎來(lái)電咨詢(xún)建站服務(wù):18980820575
JavaScript閉包
1.閉包最常用的方式就是返回一個(gè)內(nèi)聯(lián)函數(shù)(何為內(nèi)聯(lián)函數(shù)?就是在函數(shù)內(nèi)部聲明的函數(shù));
2.在JavaScript中有作用域和執(zhí)行環(huán)境的問(wèn)題,在函數(shù)內(nèi)部的變量在函數(shù)外部是無(wú)法訪(fǎng)問(wèn)的,在函數(shù)內(nèi)部卻可以得到全局變量。由于種種原因,我們有時(shí)候需要得到函數(shù)內(nèi)部的變量,可是用常規(guī)方法是得不到的,這時(shí)我們就可以創(chuàng)建一個(gè)閉包,用來(lái)在外部訪(fǎng)問(wèn)這個(gè)變量。
3.閉包的用途 主要就是上一點(diǎn)提到的讀取函數(shù)內(nèi)部變量,還有一個(gè)作用就是可以使這些變量一直保存在內(nèi)存中。
4.使用閉包要注意,由于變量被保存在內(nèi)存中,所以會(huì)對(duì)內(nèi)存造成消耗,所以不能濫用閉包。解決方法是 在退出函數(shù)之前,將不使用的局部變量全部刪除。
***還是上一套閉包的代碼吧,這樣更直觀(guān)。
function f(){ var n = 999; function f1(){ alert(n+=1); } return f1; } var result = f(); result(); // 1000 result(); // 1001 result(); // 1002
封裝:通過(guò)將一個(gè)方法或者屬性聲明為私用的,可以讓對(duì)象的實(shí)現(xiàn)細(xì)節(jié)對(duì)其他對(duì)象保密以降低對(duì)象之間的耦合程度,可以保持?jǐn)?shù)據(jù)的完整性并對(duì)其修改方式加以約束,這樣可以是代碼更可靠,更易于調(diào)試。封裝是面向?qū)ο蟮脑O(shè)計(jì)的基石。
盡管JavaScript是一門(mén)面向?qū)ο蟮恼Z(yǔ)言,可它并不具備將成員聲明為公用或私用的任何內(nèi)部機(jī)制,所以我們只能自己想辦法實(shí)現(xiàn)這種特性。下面還是通過(guò)一套完整的代碼去分析,介紹什么是私有屬性和方法,什么是特權(quán)屬性和方法,什么是公有屬性和方法,什么是公有靜態(tài)屬性和方法。
私有屬性和方法:函數(shù)有作用域,在函數(shù)內(nèi)用var 關(guān)鍵字聲明的變量在外部無(wú)法訪(fǎng)問(wèn),私有屬性和方法本質(zhì)就是你希望在對(duì)象外部無(wú)法訪(fǎng)問(wèn)的變量。
特權(quán)屬性和方法:創(chuàng)建屬性和方法時(shí)使用的this關(guān)鍵字,因?yàn)檫@些方法定義在構(gòu)造器的作用域中,所以它們可以訪(fǎng)問(wèn)到私有屬性和方法;只有那些需要直接訪(fǎng)問(wèn)私有成員的方法才應(yīng)該被設(shè)計(jì)為特權(quán)方法。
共有屬性和方法:直接鏈在prototype上的屬性和方法,不可以訪(fǎng)問(wèn)構(gòu)造器內(nèi)的私有成員,可以訪(fǎng)問(wèn)特權(quán)成員,子類(lèi)會(huì)繼承所有的共有方法。
共有靜態(tài)屬性和方法:***的理解方式就是把它想象成一個(gè)命名空間,實(shí)際上相當(dāng)于把構(gòu)造器作為命名空間來(lái)使用。
/* -- 封裝 -- */ var _packaging = function(){ //私有屬性和方法 var name = 'Darren'; var method1 = function(){ //... } //特權(quán)屬性和方法 this.title = 'JavaScript Design Patterns' ; this.getName = function(){ return name; } } //共有靜態(tài)屬性和方法 _packaging._name = 'Darren code'; _packaging.alertName = function(){ alert(_packaging._name); } //共有屬性和方法 _packaging.prototype = { init:function(){ //... } }
繼承:繼承本身就是一個(gè)抽象的話(huà)題,在JavaScript中繼承更是一個(gè)復(fù)雜的話(huà)題,因?yàn)镴avaScript想要實(shí)現(xiàn)繼承有兩種實(shí)現(xiàn)方式,分別是類(lèi)式繼承和原型式繼承,每種實(shí)現(xiàn)的方式都需要采取不少措施,下面本人通過(guò)分析例子的方式講解JavaScript中這個(gè)很重要的話(huà)題。
/* -- 類(lèi)式繼承 -- */ //先聲明一個(gè)超類(lèi) function Person(name){ this.name = name; } //給這個(gè)超類(lèi)的原型對(duì)象上添加方法 getName Person.prototype.getName = function(){ return this.name; } //實(shí)例化這個(gè)超類(lèi) var a = new Person('Darren1') alert(a.getName()); //再聲明類(lèi) function Programmer(name,sex){ //這個(gè)類(lèi)中要調(diào)用超類(lèi)Person的構(gòu)造函數(shù),并將參數(shù)name傳給它 Person.call(this,name); this.sex = sex; } //這個(gè)子類(lèi)的原型對(duì)象等于超類(lèi)的實(shí)例 Programmer.prototype = new Person(); //因?yàn)樽宇?lèi)的原型對(duì)象等于超類(lèi)的實(shí)例,所以prototype.constructor這個(gè)方法也等于超類(lèi)構(gòu)造函數(shù),你可以自己測(cè)試一下,如果沒(méi)這一步,alert(Programmer.prototype.constructor),這個(gè)是Person超類(lèi)的引用,所以要從新賦值為自己本身 Programmer.prototype.constructor = Programmer; //子類(lèi)本身添加了getSex 方法 Programmer.prototype.getSex = function(){ return this.sex; } //實(shí)例化這個(gè)子類(lèi) var _m = new Programmer('Darren2','male'); //自身的方法 alert(_m.getSex()); //繼承超類(lèi)的方法 alert(_m.getName());
代碼都不難,只要對(duì) 原型鏈有基礎(chǔ)就能理解。類(lèi)式繼承模式是JavaScript繼承主要的模式,幾乎所有用面向?qū)ο蠓绞骄帉?xiě)的JavaScript代碼中都用到了這種繼承,又因?yàn)樵诟鞣N流行語(yǔ)言中只有JavaScript使用原型式繼承,因此***還是使用類(lèi)式繼承??墒且煜avaScript語(yǔ)言,原型繼承也是我們必須所了解的,至于在項(xiàng)目中是否使用就得看個(gè)人編碼風(fēng)格了。
/* -- 原型式繼承 -- */ //clone()函數(shù)用來(lái)創(chuàng)建新的類(lèi)Person對(duì)象 var clone = function(obj){ var _f = function(){}; //這句是原型式繼承最核心的地方,函數(shù)的原型對(duì)象為對(duì)象字面量 _f.prototype = obj; return new _f; } //先聲明一個(gè)對(duì)象字面量 var Person = { name:'Darren', getName:function(){ return this.name; } } //不需要定義一個(gè)Person的子類(lèi),只要執(zhí)行一次克隆即可 var Programmer = clone(Person); //可以直接獲得Person提供的默認(rèn)值,也可以添加或者修改屬性和方法 alert(Programmer.getName()) Programmer.name = 'Darren2' alert(Programmer.getName()) //聲明子類(lèi),執(zhí)行一次克隆即可 var Someone = clone(Programmer);
JavaScript設(shè)計(jì)模式的作用 - 提高代碼的重用性,可讀性,使代碼更容易的維護(hù)和擴(kuò)展。
1.單體模式,工廠(chǎng)模式,橋梁模式個(gè)人認(rèn)為這個(gè)一個(gè)優(yōu)秀前端必須掌握的模式,對(duì)抽象編程和接口編程都非常有好處。
2.裝飾者模式和組合模式有很多相似的地方,它們都與所包裝的對(duì)象實(shí)現(xiàn)同樣的接口并且會(huì)把任何方法的調(diào)用傳遞給這些對(duì)象。裝飾者模式和組合模式是本人描述的較吃力的兩個(gè)模式,我個(gè)人其實(shí)也沒(méi)用過(guò),所以查了很多相關(guān)資料和文檔,請(qǐng)大家海涵。
3.門(mén)面模式是個(gè)非常有意思的模式,幾乎所有的JavaScript庫(kù)都會(huì)用到這個(gè)模式,假如你有逆向思維或者逆向編程的經(jīng)驗(yàn),你會(huì)更容易理解這個(gè)模式(聽(tīng)起來(lái)有挑戰(zhàn),其實(shí)一接觸你就知道這是個(gè)很簡(jiǎn)單的模式);還有配置器模式得和門(mén)面模式一塊拿來(lái)說(shuō),這個(gè)模式對(duì)現(xiàn)有接口進(jìn)行包裝,合理運(yùn)用可以很多程度上提高開(kāi)發(fā)效率。這兩個(gè)模式有相似的地方,所以一塊理解的話(huà)相信都會(huì)很快上手的。
4.享元模式是一種以?xún)?yōu)化為目的的模式。
5.代理模式主要用于控制對(duì)象的訪(fǎng)問(wèn),包括推遲對(duì)其創(chuàng)建需要耗用大量計(jì)算資源的類(lèi)得實(shí)例化。
6.觀(guān)察者模式用于對(duì)對(duì)象的狀態(tài)進(jìn)行觀(guān)察,并且當(dāng)它發(fā)生變化時(shí)能得到通知的方法。用于讓對(duì)象對(duì)事件進(jìn)行監(jiān)聽(tīng)以便對(duì)其作出響應(yīng)。觀(guān)察者模式也被稱(chēng)為“訂閱者模式”。
7.命令模式是對(duì)方法調(diào)用進(jìn)行封裝的方式,用命名模式可以對(duì)方法調(diào)用進(jìn)行參數(shù)化和傳遞,然后在需要的時(shí)候再加以執(zhí)行。
8.職責(zé)鏈模式用來(lái)消除請(qǐng)求的發(fā)送者和接收者之間的耦合。
JavaScript設(shè)計(jì)模式都有哪些?
單體(Singleton)模式:絕對(duì)是JavaScript中最基本最有用的模式。
單體在JavaScript的有多種用途,它用來(lái)劃分命名空間??梢詼p少網(wǎng)頁(yè)中全局變量的數(shù)量(在網(wǎng)頁(yè)中使用全局變量有風(fēng)險(xiǎn));可以在多人開(kāi)發(fā)時(shí)避免代碼的沖突(使用合理的命名空間)等等。
在中小型項(xiàng)目或者功能中,單體可以用作命名空間把自己的代碼組織在一個(gè)全局變量名下;在稍大或者復(fù)雜的功能中,單體可以用來(lái)把相關(guān)代碼組織在一起以便日后好維護(hù)。
使用單體的方法就是用一個(gè)命名空間包含自己的所有代碼的全局對(duì)象,示例:
var functionGroup = { name:'Darren', method1:function(){ //code }, init:function(){ //code } }
或者
var functionGroup = new function myGroup(){ this.name = 'Darren'; this.getName = function(){ return this.name } this.method1 = function(){} ... }
工廠(chǎng)(Factory)模式:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴(lài)對(duì)象的接口,而無(wú)需指定他們具體的類(lèi)。
工廠(chǎng)就是把成員對(duì)象的創(chuàng)建工作轉(zhuǎn)交給一個(gè)外部對(duì)象,好處在于消除對(duì)象之間的耦合(何為耦合?就是相互影響)。通過(guò)使用工廠(chǎng)方法而不是new關(guān)鍵字及具體類(lèi),可以把所有實(shí)例化的代碼都集中在一個(gè)位置,有助于創(chuàng)建模塊化的代碼,這才是工廠(chǎng)模式的目的和優(yōu)勢(shì)。
舉個(gè)例子:你有一個(gè)大的功能要做,其中有一部分是要考慮擴(kuò)展性的,那么這部分代碼就可以考慮抽象出來(lái),當(dāng)做一個(gè)全新的對(duì)象做處理。好處就是將來(lái)擴(kuò)展的時(shí)候容易維護(hù) - 只需要操作這個(gè)對(duì)象內(nèi)部方法和屬性,達(dá)到了動(dòng)態(tài)實(shí)現(xiàn)的目的。非常有名的一個(gè)示例 - XHR工廠(chǎng):
var XMLHttpFactory = function(){};//這是一個(gè)簡(jiǎn)單工廠(chǎng)模式 XMLHttpFactory.createXMLHttp = function(){ var XMLHttp = null; if (window.XMLHttpRequest){ XMLHttp = new XMLHttpRequest() }else if (window.ActiveXObject){ XMLHttp = new ActiveXObject("Microsoft.XMLHTTP") } return XMLHttp; } //XMLHttpFactory.createXMLHttp()這個(gè)方法根據(jù)當(dāng)前環(huán)境的具體情況返回一個(gè)XHR對(duì)象。 var AjaxHander = function(){ var XMLHttp = XMLHttpFactory.createXMLHttp(); ... }
工廠(chǎng)模式又區(qū)分簡(jiǎn)單工廠(chǎng)模式和抽象工廠(chǎng)模式,上面介紹的是簡(jiǎn)單工廠(chǎng)模式,這種模式用的更多也更簡(jiǎn)單易用。抽象工廠(chǎng)模式的使用方法就是 - 先設(shè)計(jì)一個(gè)抽象類(lèi),這個(gè)類(lèi)不能被實(shí)例化,只能用來(lái)派生子類(lèi),***通過(guò)對(duì)子類(lèi)的擴(kuò)展實(shí)現(xiàn)工廠(chǎng)方法。 示例:
var XMLHttpFactory = function(){}; //這是一個(gè)抽象工廠(chǎng)模式 XMLHttpFactory.prototype = { //如果真的要調(diào)用這個(gè)方法會(huì)拋出一個(gè)錯(cuò)誤,它不能被實(shí)例化,只能用來(lái)派生子類(lèi) createFactory:function(){ throw new Error('This is an abstract class'); } } //派生子類(lèi),文章開(kāi)始處有基礎(chǔ)介紹那有講解繼承的模式,不明白可以去參考原理 var XHRHandler = function(){ XMLHttpFactory.call(this); }; XHRHandler.prototype = new XMLHttpFactory(); XHRHandler.prototype.constructor = XHRHandler; //重新定義createFactory 方法 XHRHandler.prototype.createFactory = function(){ var XMLHttp = null; if (window.XMLHttpRequest){ XMLHttp = new XMLHttpRequest() }else if (window.ActiveXObject){ XMLHttp = new ActiveXObject("Microsoft.XMLHTTP") } return XMLHttp; }
橋接(bridge)模式:在實(shí)現(xiàn)API的時(shí)候,橋梁模式灰常有用。在所有模式中,這種模式最容易立即付諸實(shí)施。
橋梁模式可以用來(lái)弱化它與使用它的類(lèi)和對(duì)象之間的耦合,就是將抽象與其實(shí)現(xiàn)隔離開(kāi)來(lái),以便二者獨(dú)立變化;這種模式對(duì)于JavaScript中常見(jiàn)的時(shí)間驅(qū)動(dòng)的編程有很大益處,橋梁模式最常見(jiàn)和實(shí)際的應(yīng)用場(chǎng)合之一是時(shí)間監(jiān)聽(tīng)器回調(diào)函數(shù)。先分析一個(gè)不好的示例:
element.onclick = function(){ new setLogFunc(); };
為什么說(shuō)這個(gè)示例不好,因?yàn)閺倪@段代碼中無(wú)法看出那個(gè)LogFunc方法要顯示在什么地方,它有什么可配置的選項(xiàng)以及應(yīng)該怎么去修改它。換一種說(shuō)法就是,橋梁模式的要訣就是讓接口“可橋梁”,實(shí)際上也就是可配置。把頁(yè)面中一個(gè)個(gè)功能都想象成模塊,接口可以使得模塊之間的耦合降低。
掌握橋梁模式的正確使用收益的不只是你,還有那些負(fù)責(zé)維護(hù)你代碼的人。把抽象于其實(shí)現(xiàn)隔離開(kāi),可獨(dú)立地管理軟件的各個(gè)部分,bug也因此更容易查找。
橋梁模式目的就是讓API更加健壯,提高組件的模塊化程度,促成更簡(jiǎn)潔的實(shí)現(xiàn),并提高抽象的靈活性。一個(gè)好的示例:
element.onclick = function(){//API可控制性提高了,使得這個(gè)API更加健壯 new someFunction(element,param,callback); }
注:橋梁模式還可以用于連接公開(kāi)的API代碼和私有的實(shí)現(xiàn)代碼,還可以把多個(gè)類(lèi)連接在一起。在文章封裝介紹的部分提到過(guò)特權(quán)方法,也是橋梁模式的一種特例?!禞S設(shè)計(jì)模式》上找的示例,加深大家對(duì)這個(gè)模式的理解:
//錯(cuò)誤的方式 //這個(gè)API根據(jù)事件監(jiān)聽(tīng)器回調(diào)函數(shù)的工作機(jī)制,事件對(duì)象被作為參數(shù)傳遞給這個(gè)函數(shù)。本例中并沒(méi)有使用這個(gè)參數(shù),而只是從this對(duì)象獲取ID。 addEvent(element,'click',getBeerById); function(e){ var id = this.id; asyncRequest('GET','beer.url?id=' + id,function(resp){ //Callback response console.log('Requested Beer: ' + resp.responseText); }); } //好的方式 //從邏輯上分析,把id傳給getBeerById函數(shù)式合情理的,且回應(yīng)結(jié)果總是通過(guò)一個(gè)毀掉函數(shù)返回。這么理解,我們現(xiàn)在做的是針對(duì)接口而不是實(shí)現(xiàn)進(jìn)行編程,用橋梁模式把抽象隔離開(kāi)來(lái)。 function getBeerById(id,callback){ asyncRequest('GET','beer.url?id=' + id,function(resp){ callback(resp.responseText) }); } addEvent(element,'click',getBeerByIdBridge); function getBeerByIdBridge(e){ getBeerById(this.id,function(beer){ console.log('Requested Beer: ' + beer); }); }
裝飾者(Decorator)模式:這個(gè)模式就是為對(duì)象增加功能(或方法)。
動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就擴(kuò)展功能而言,它比生成子類(lèi)方式更為靈活。
裝飾者模式和組合模式有很多共同點(diǎn),它們都與所包裝的對(duì)象實(shí)現(xiàn)統(tǒng)一的接口并且會(huì)把任何方法條用傳遞給這些對(duì)象。可是組合模式用于把眾多子對(duì)象組織為一個(gè)整體,而裝飾者模式用于在不修改現(xiàn)有對(duì)象或從派生子類(lèi)的前提下為其添加方法。
裝飾者的運(yùn)作過(guò)程是透明的,這就是說(shuō)你可以用它包裝其他對(duì)象,然后繼續(xù)按之前使用那么對(duì)象的方法來(lái)使用,從下面的例子中就可以看出。還是從代碼中理解吧:
//創(chuàng)建一個(gè)命名空間為myText.Decorations var myText= {}; myText.Decorations={}; myText.Core=function(myString){ this.show = function(){return myString;} } //***次裝飾 myText.Decorations.addQuestuibMark =function(myString){ this.show = function(){return myString.show()+'?';}; } //第二次裝飾 myText.Decorations.makeItalic = function(myString){ this.show = function(){return '<li>'+myString.show()+'</li>'}; } //得到myText.Core的實(shí)例 var theString = new myText.Core('this is a sample test String'); alert(theString.show());//output 'this is a sample test String' theString = new myText.Decorations.addQuestuibMark(theString); alert(theString.show());//output 'this is a sample test String?' theString = new myText.Decorations.makeItalic (theString); alert(theString.show());//output '<li>this is a sample test String</li>'
從這個(gè)示例中可以看出,這一切都可以不用事先知道組件對(duì)象的接口,甚至可以動(dòng)態(tài)的實(shí)現(xiàn),在為現(xiàn)有對(duì)象增添特性這方面,裝飾者模式有極大的靈活性。
如果需要為類(lèi)增加特性或者方法,而從該類(lèi)派生子類(lèi)的解決辦法并不實(shí)際的話(huà),就應(yīng)該使用裝飾者模式。派生子類(lèi)之所以會(huì)不實(shí)際最常見(jiàn)的原因是需要添加的特性或方法的數(shù)量要求使用大量子類(lèi)。
組合(Composite)模式:將對(duì)象組合成樹(shù)形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)。它使得客戶(hù)對(duì)單個(gè)對(duì)象和復(fù)合對(duì)象的使用具有一致性。
組合模式是一種專(zhuān)為創(chuàng)建Web上的動(dòng)態(tài)用戶(hù)界面而量身定制的模式。使用這種模式,可以用一條命令在多個(gè)對(duì)象上激發(fā)復(fù)雜的或遞歸的行為。組合模式擅長(zhǎng)于對(duì)大批對(duì)象進(jìn)行操作。
組合模式的好處:1.程序員可以用同樣的方法處理對(duì)象的集合與其中的特定子對(duì)象;2.它可以用來(lái)把一批子對(duì)象組織成樹(shù)形結(jié)構(gòu),并且使整棵樹(shù)都可被便利。
組合模式適用范圍:1.存在一批組織成某處層次體系的對(duì)象(具體結(jié)構(gòu)可能在開(kāi)發(fā)期間無(wú)法知道);2.希望對(duì)這批對(duì)象或其中的一部分對(duì)象實(shí)話(huà)一個(gè)操作。
其實(shí)組合模式就是將一系列相似或相近的對(duì)象組合在一個(gè)大的對(duì)象,由這個(gè)大對(duì)象提供一些常用的接口來(lái)對(duì)這些小對(duì)象進(jìn)行操作,代碼可重用,對(duì)外操作簡(jiǎn)單。例如:對(duì)form內(nèi)的元素,不考慮頁(yè)面設(shè)計(jì)的情況下,一般就剩下input了,對(duì)于這些input都有name和value的屬性,因此可以將這些input元素作為form對(duì)象的成員組合起來(lái),form對(duì)象提供對(duì)外的接口,便可以實(shí)現(xiàn)一些簡(jiǎn)單的操作,比如設(shè)置某個(gè)input的value,添加/刪除某個(gè)input等等。
這種模式描述起來(lái)比較吃力,我從《JS設(shè)計(jì)模式》上找個(gè)一個(gè)實(shí)例,大家還是看代碼吧:先創(chuàng)建組合對(duì)象類(lèi)
// DynamicGallery Class var DynamicGallery = function (id) { // 實(shí)現(xiàn)Composite,GalleryItem組合對(duì)象類(lèi) this.children = []; this.element = document.createElement('div'); this.element.id = id; this.element.className = 'dynamic-gallery'; } DynamicGallery.prototype = { // 實(shí)現(xiàn)Composite組合對(duì)象接口 add: function (child) { this.children.push(child); this.element.appendChild(child.getElement()); }, remove: function (child) { for (var node, i = 0; node = this.getChild(i); i++) { if (node == child) { this.children.splice(i, 1); break; } } this.element.removeChild(child.getElement()); }, getChild: function (i) { return this.children[i]; }, // 實(shí)現(xiàn)DynamicGallery組合對(duì)象接口 hide: function () { for (var node, i = 0; node = this.getChild(i); i++) { node.hide(); } this.element.style.display = 'none'; }, show: function () { this.element.style.display = 'block'; for (var node, i = 0; node = getChild(i); i++) { node.show(); } }, // 幫助方法 getElement: function () { return this.element; } }
再創(chuàng)建葉對(duì)象類(lèi)
var GalleryImage = function (src) { // 實(shí)現(xiàn)Composite和GalleryItem組合對(duì)象中所定義的方法 this.element = document.createElement('img'); this.element.className = 'gallery-image'; this.element.src = src; } GalleryImage.prototype = { // 實(shí)現(xiàn)Composite接口 // 這些是葉結(jié)點(diǎn),所以我們不用實(shí)現(xiàn)這些方法,我們只需要定義即可 add: function () { }, remove: function () { }, getChild: function () { }, // 實(shí)現(xiàn)GalleryItem接口 hide: function () { this.element.style.display = 'none'; }, show: function () { this.element.style.display = ''; }, // 幫助方法 getElement: function () { return this.element; } }
現(xiàn)在我們可以使用這兩個(gè)類(lèi)來(lái)管理圖片:
var topGallery = new DynamicGallery('top-gallery'); topGallery.add(new GalleryImage('/img/image-1.jpg')); topGallery.add(new GalleryImage('/img/image-2.jpg')); topGallery.add(new GalleryImage('/img/image-3.jpg')); var vacationPhotos = new DyamicGallery('vacation-photos'); for(var i = 0, i < 30; i++){ vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg')); } topGallery.add(vacationPhotos); topGallery.show(); vacationPhotos.hide();
門(mén)面(facade)模式:門(mén)面模式是幾乎所有JavaScript庫(kù)的核心原則
子系統(tǒng)中的一組接口提供一個(gè)一致的界面,門(mén)面模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用,簡(jiǎn)單的說(shuō)這是一種組織性的模式,它可以用來(lái)修改類(lèi)和對(duì)象的接口,使其更便于使用。
門(mén)面模式的兩個(gè)作用:1.簡(jiǎn)化類(lèi)的接口;2.消除類(lèi)與使用它的客戶(hù)代碼之間的耦合。
門(mén)面模式的使用目的就是圖方面。
想象一下計(jì)算機(jī)桌面上的那些快捷方式圖標(biāo),它們就是在扮演一個(gè)把用戶(hù)引導(dǎo)至某個(gè)地方的接口的角色,每次操作都是間接的執(zhí)行一些幕后的命令。
你在看這篇的博客的時(shí)候我就假設(shè)你已經(jīng)有JavaScript的使用經(jīng)驗(yàn)了,那么你一定寫(xiě)過(guò)或者看過(guò)這樣的代碼:
var addEvent = function(el,type,fn){ if(window.addEventListener){ el.addEventListener(type,fn); }else if(window.attachEvent){ el.attachEvent('on'+type,fn); }else{ el['on'+type] = fn; } }
這個(gè)就是一個(gè)JavaScript中常見(jiàn)的事件監(jiān)聽(tīng)器函數(shù),這個(gè)函數(shù)就是一個(gè)基本的門(mén)面,有了它,就有了為DOM節(jié)點(diǎn)添加事件監(jiān)聽(tīng)器的簡(jiǎn)便方法。
現(xiàn)在要說(shuō)門(mén)面模式的精華部分了,為什么說(shuō)JavaScript庫(kù)幾乎都會(huì)用這種模式類(lèi)。假如現(xiàn)在要設(shè)計(jì)一個(gè)庫(kù),那么***把其中所有的工具元素放在一起,這樣更好用,訪(fǎng)問(wèn)起來(lái)更簡(jiǎn)便。看代碼:
//_model.util是一個(gè)命名空間 _myModel.util.Event = { getEvent:function(e){ return e|| window.event; }, getTarget:function(e){ return e.target||e.srcElement; }, preventDefault:function(e){ if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue = false; } } }; //事件工具大概就是這么一個(gè)套路,然后結(jié)合addEvent函數(shù)使用 addEvent(document.getElementsByTagName('body')[0],'click',function(e){ alert(_myModel.util.Event.getTarget(e)); });
個(gè)人認(rèn)為,在處理游覽器差異問(wèn)題時(shí)***的解決辦法就是把這些差異抽取的門(mén)面方法中,這樣可以提供一個(gè)更一致的接口,addEvent函數(shù)就是一個(gè)例子。
適配置器(Adapter)模式:將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶(hù)希望的另外一個(gè)接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類(lèi)可以一起工作,使用這種模式的對(duì)象又叫包裝器,因?yàn)樗麄兪窃谟靡粋€(gè)新的接口包裝另一個(gè)對(duì)象。
從表面上看,它和門(mén)面模式有點(diǎn)相似,差別在于它們?nèi)绾胃淖兘涌?,門(mén)面模式展現(xiàn)的是一個(gè)簡(jiǎn)化的接口,它并不提供額外的選擇,而適配器模式則要把一個(gè)接口轉(zhuǎn)換為另一個(gè)接口,它并不會(huì)濾除某些能力,也不會(huì)簡(jiǎn)化接口。先來(lái)一個(gè)簡(jiǎn)單的示例看看:
//假如有一個(gè)3個(gè)字符串參數(shù)的函數(shù),但是現(xiàn)在擁有的卻是一個(gè)包含三個(gè)字符串元素的對(duì)象,那么就可以用一個(gè)配置器來(lái)銜接二者 var clientObject = { str1:'bat', str2:'foo', str3:'baz' } function interfaceMethod(str1,str2,str3){ alert(str1) } //配置器函數(shù) function adapterMethod(o){ interfaceMethod(o.str1, o.str2, o.str3); } adapterMethod(clientObject) //adapterMethod函數(shù)的作為就在于對(duì)interfaceMethod函數(shù)進(jìn)行包裝,并把傳遞給它的參數(shù)轉(zhuǎn)換為后者需要的形式。
適配器模式的工作機(jī)制是:用一個(gè)新的接口對(duì)現(xiàn)有類(lèi)得接口進(jìn)行包裝。
示例:適配兩個(gè)庫(kù)。下面的例子要實(shí)現(xiàn)的是從Prototype庫(kù)的$函數(shù)到Y(jié)UI的get方法的轉(zhuǎn)換。
//先看它們?cè)诮涌诜矫娴牟顒e //Prototype $ function function $(){ var elements = new Array(); for(var i=0;i<arguments.length;i++){ var element = arguments[i]; if(typeof element == 'string'){ element = document.getElementById(element); } if(typeof.length ==1) return element; elements.push(element); } return elements; } //YUI get method YAHOO.util.Dom.get = function(el){ if(YAHOO.lang.isString(el)){ return document.getElementById(el); } if(YAHOO.lang.isArray(el)){ var c =[]; for(var i=0,len=el.length;i<len;++i){ c[c.length] = YAHOO.util.Dom.get(el[i]); } return c; } if(el){ return el; } return null; } //二者區(qū)別就在于get具有一個(gè)參數(shù),且可以是HTML,字符串或者數(shù)組;而$木有正是的參數(shù),允許使用者傳入任意數(shù)目的參數(shù),不管HTML還是字符串。 //如果需要從使用Prototype的$函數(shù)改為使用YUI的get方法(或者相反,那么用適配器模式其實(shí)很簡(jiǎn)單) function PrototypeToYUIAdapter(){ return YAHOO.util.Dom.get(arguments); } function YUIToPrototypeAdapter(el){ return $.apply(window,el instanceof Array?el:[el]); }
享元(Flyweight)模式:運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度的對(duì)象。
享元模式可以避免大量非常相似類(lèi)的開(kāi)銷(xiāo)。在程序設(shè)計(jì)中有時(shí)需要生成大量細(xì)粒度的類(lèi)實(shí)例來(lái)表示數(shù)據(jù)。如果發(fā)現(xiàn)這些實(shí)例除了幾個(gè)參數(shù)外基本傷都是相同的,有時(shí)就能夠受大幅度第減少需要實(shí)例化的類(lèi)的數(shù)量。如果能把這些參數(shù)移到類(lèi)實(shí)例外面,在方法調(diào)用時(shí)將他們傳遞進(jìn)來(lái),就可以通過(guò)共享大幅度地減少單個(gè)實(shí)例的數(shù)目。
從實(shí)際出發(fā)說(shuō)說(shuō)自己的理解吧。
組成部分
“享元”:抽離出來(lái)的外部操作和數(shù)據(jù);
“工廠(chǎng)”:創(chuàng)造對(duì)象的工廠(chǎng);
“存儲(chǔ)器”:存儲(chǔ)實(shí)例對(duì)象的對(duì)象或數(shù)組,供“享元”來(lái)統(tǒng)一控制和管理。
應(yīng)用場(chǎng)景
1. 頁(yè)面存在大量資源密集型對(duì)象;
2. 這些對(duì)象具備一定的共性,可以抽離出公用的操作和數(shù)據(jù)
關(guān)鍵
1. 合理劃分內(nèi)部和外部數(shù)據(jù)。
既要保持每個(gè)對(duì)象的模塊性、保證享元的獨(dú)立、可維護(hù),又要盡可能多的抽離外部數(shù)據(jù)。
2. 管理所有實(shí)例
既然抽離出了外部數(shù)據(jù)和操作,那享元就必須可以訪(fǎng)問(wèn)和控制實(shí)例對(duì)象。在JavaScript這種動(dòng)態(tài)語(yǔ)言中,這個(gè)需求是很容易實(shí)現(xiàn)的:我們可以把工廠(chǎng)生產(chǎn)出的對(duì)象簡(jiǎn)單的扔在一個(gè)數(shù)組中。為每個(gè)對(duì)象設(shè)計(jì)暴露給外部的方法,便于享元的控制。
優(yōu)點(diǎn)
1. 將能耗大的操作抽離成一個(gè),在資源密集型系統(tǒng)中,可大大減少資源和內(nèi)存占用;
2. 職責(zé)封裝,這些操作獨(dú)立修改和維護(hù);
缺點(diǎn)
1. 增加了實(shí)現(xiàn)復(fù)雜度。
將原本由一個(gè)工廠(chǎng)方法實(shí)現(xiàn)的功能,修改為了一個(gè)享元+一個(gè)工廠(chǎng)+一個(gè)存儲(chǔ)器。
2. 對(duì)象數(shù)量少的情況,可能會(huì)增大系統(tǒng)開(kāi)銷(xiāo)。
示例:
//汽車(chē)登記示例 var Car = function(make,model,year,owner,tag,renewDate){ this.make=make; this.model=model; this.year=year; this.owner=owner; this.tag=tag; this.renewDate=renewDate; } Car.prototype = { getMake:function(){ return this.make; }, getModel:function(){ return this.model; }, getYear:function(){ return this.year; }, transferOwner:function(owner,tag,renewDate){ this.owner=owner; this.tag=tag; this.renewDate=renewDate; }, renewRegistration:function(renewDate){ this.renewDate=renewDate; } } //數(shù)據(jù)量小到?jīng)]多大的影響,數(shù)據(jù)量大的時(shí)候?qū)τ?jì)算機(jī)內(nèi)存會(huì)產(chǎn)生壓力,下面介紹享元模式優(yōu)化后 //包含核心數(shù)據(jù)的Car類(lèi) var Car=function(make,model,year){ this.make=make; this.model=model; this.year=year; } Car.prototype={ getMake:function(){ return this.make; }, getModel:function(){ return this.model; }, getYear:function(){ return this.year; } } //中間對(duì)象,用來(lái)實(shí)例化Car類(lèi) var CarFactory=(function(){ var createdCars = {}; return { createCar:function(make,model,year){ var car=createdCars[make+"-"+model+"-"+year]; return car ? car : createdCars[make + '-' + model + '-' + year] =(new Car(make,model,year)); } } })(); //數(shù)據(jù)工廠(chǎng),用來(lái)處理Car的實(shí)例化和整合附加數(shù)據(jù) var CarRecordManager = (function() { var carRecordDatabase = {}; return { addCarRecord:function(make,model,year,owner,tag,renewDate){ var car = CarFactory.createCar(make, model, year); carRecordDatabase[tag]={ owner:owner, tag:tag, renewDate:renewDate, car:car } }, transferOwnership:function(tag, newOwner, newTag, newRenewDate){ var record=carRecordDatabase[tag]; record.owner = newOwner; record.tag = newTag; record.renewDate = newRenewDate; }, renewRegistration:function(tag,newRenewDate){ carRecordDatabase[tag].renewDate=newRenewDate; }, getCarInfo:function(tag){ return carRecordDatabase[tag]; } } })();
代理(Proxy)模式:此模式最基本的形式是對(duì)訪(fǎng)問(wèn)進(jìn)行控制。代理對(duì)象和另一個(gè)對(duì)象(本體)實(shí)現(xiàn)的是同樣的接口,可是實(shí)際上工作還是本體在做,它才是負(fù)責(zé)執(zhí)行所分派的任務(wù)的那個(gè)對(duì)象或類(lèi),代理對(duì)象不會(huì)在另以對(duì)象的基礎(chǔ)上修改任何方法,也不會(huì)簡(jiǎn)化那個(gè)對(duì)象的接口。
舉一個(gè)具體的情況:如果那個(gè)對(duì)象在某個(gè)遠(yuǎn)端服務(wù)器上,直接操作這個(gè)對(duì)象因?yàn)榫W(wǎng)絡(luò)速度原因可能比較慢,那我們可以先用Proxy來(lái)代替那個(gè)對(duì)象。
總之對(duì)于開(kāi)銷(xiāo)較大的對(duì)象,只有在使用它時(shí)才創(chuàng)建,這個(gè)原則可以為我們節(jié)省很多內(nèi)存。《JS設(shè)計(jì)模式》上的圖書(shū)館示例:
var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']); var Book = function(isbn, title, author) { //... } // implements Publication implements(Book,Publication); /* Library interface. */ var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']); /* PublicLibrary class. */ var PublicLibrary = function(books) { //... }; // implements Library implements(PublicLibrary,Library); PublicLibrary.prototype = { findBooks: function(searchString) { //... }, checkoutBook: function(book) { //... }, returnBook: function(book) { //... } }; /* PublicLibraryProxy class, a useless proxy. */ var PublicLibraryProxy = function(catalog) { this.library = new PublicLibrary(catalog); }; // implements Library implements(PublicLibraryProxy,Library); PublicLibraryProxy.prototype = { findBooks: function(searchString) { return this.library.findBooks(searchString); }, checkoutBook: function(book) { return this.library.checkoutBook(book); }, returnBook: function(book) { return this.library.returnBook(book); } };
觀(guān)察者(Observer)模式:定義對(duì)象間的一種一對(duì)多的依賴(lài)關(guān)系,以便當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴(lài)于它的對(duì)象都得到通知并自動(dòng)刷新。
觀(guān)察者模式中存在兩個(gè)角色,觀(guān)察者和被觀(guān)察者。在DOM的編程環(huán)境中的高級(jí)事件模式中,事件監(jiān)聽(tīng)器說(shuō)到底就是一種內(nèi)置的觀(guān)察者。事件處理器(handler)和時(shí)間監(jiān)聽(tīng)器(listener)并不是一回事,前者就是一種把事件傳給與其關(guān)聯(lián)的函數(shù)的手段,而在后者中,一個(gè)時(shí)間可以與幾個(gè)監(jiān)聽(tīng)器關(guān)聯(lián),每個(gè)監(jiān)聽(tīng)器都能獨(dú)立于其他監(jiān)聽(tīng)器而改變。
//使用時(shí)間監(jiān)聽(tīng)器可以讓多個(gè)函數(shù)相應(yīng)一個(gè)事件 var fn1 = function(){ //code } var fn2 = function(){ //code } addEvent(element,'click',fn1); addEvent(element,'click',fn2) //而時(shí)間處理函數(shù)就辦不到 element.onclick = fn1; element.onclick = fn2;
觀(guān)察者模式是開(kāi)發(fā)基于行為的應(yīng)用程序的有力手段,前端程序員可做的就是借助一個(gè)事件監(jiān)聽(tīng)器替你處理各種行為,從而降低內(nèi)存消耗和提高互動(dòng)性能。
命令(Command)模式:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶(hù)進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可取消的操作。
命令對(duì)象是一個(gè)操作和用來(lái)調(diào)用這個(gè)操作的對(duì)象的結(jié)合體,所有的命名對(duì)象都有一個(gè)執(zhí)行操作,其用途就是調(diào)用命令對(duì)象所綁定的操作。示例:
car Calculator={ add:function(x,y){ return x+y; }, substract:function(x,y){ return x-y; }, multiply:function(x,y){ return x*y; }, divide:function(x,y){ return x/y; } } Calculator.calc = function(command){ return Calculator[command.type](command.op1,command.opd2) }; Calculator.calc({type:'add',op1:1,op2:1}); Calculator.calc({type:'substract',op1:5,op2:2}); Calculator.calc({type:'multiply',op1:5,op2:2}); Calculator.calc({type:'divide',op1:8,op2:4});
命名模式的主要用途是把調(diào)用對(duì)象(用戶(hù)界面,API和代理等)與實(shí)現(xiàn)操作的對(duì)象隔離開(kāi),也就是說(shuō)使對(duì)象間的互動(dòng)方式需要更高的模塊化時(shí)都可以用到這種模式。
職責(zé)鏈(Chain Of Responsibility)模式:為解除請(qǐng)求的發(fā)送者和接收者之間耦合,而使多個(gè)對(duì)象都有機(jī)會(huì)處理這個(gè)請(qǐng)求。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它。
職責(zé)鏈由多個(gè)不同類(lèi)型的對(duì)象組成:發(fā)送者是發(fā)出請(qǐng)求的對(duì)象,而接收者則是接收請(qǐng)求并且對(duì)其進(jìn)行處理或傳遞的對(duì)象,請(qǐng)求本身有時(shí)也是一個(gè)對(duì)象,它封裝著與操作有關(guān)的所有數(shù)據(jù)。
典型的流程大致是:
1.發(fā)送者知道鏈中***個(gè)接收者,它向這個(gè)接收者發(fā)出請(qǐng)求。
2.每一個(gè)接收者都對(duì)請(qǐng)求進(jìn)行分析,然后要么處理它,要么將其往下傳。
3.每一個(gè)接收者知道的其他對(duì)象只有一個(gè),即它在鏈中的下家。
4.如果沒(méi)有任何接收者處理請(qǐng)求,那么請(qǐng)求將從鏈上離開(kāi),不同的實(shí)現(xiàn)對(duì)此也有不同的反應(yīng),一般會(huì)拋出一個(gè)錯(cuò)誤。
職責(zé)鏈模式的適用范圍:1.有多個(gè)的對(duì)象可以處理一個(gè)請(qǐng)求,哪個(gè)對(duì)象處理該請(qǐng)求運(yùn)行時(shí)刻自動(dòng)確定;2.想在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求;3.可處理一個(gè)請(qǐng)求的對(duì)象集合需要被動(dòng)態(tài)指定。
到此,關(guān)于“JavaScript閉包以及幾種設(shè)計(jì)模式的詳細(xì)介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
新聞標(biāo)題:JavaScript閉包以及幾種設(shè)計(jì)模式的詳細(xì)介紹
瀏覽地址:http://m.newbst.com/article28/jhshjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、商城網(wǎng)站、App開(kāi)發(fā)、微信公眾號(hào)、云服務(wù)器、動(dòng)態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)