摘要:依賴于接口的設(shè)計模式下面列出的設(shè)計模式,尤其依賴接口工廠模式。這些私用的靜態(tài)成員可以從構(gòu)造器內(nèi)部訪問,這意味著所有私用函數(shù)和特權(quán)函數(shù)都能訪問它們。構(gòu)造器靜態(tài)特權(quán)方法封裝之弊私用方法很難進(jìn)行單元測試。
1.弱類型語言
2.初談閉包在JavaScript中,定義變量時不必聲明其類型。但這并不意味著變量沒有類型。一個變量可以屬于幾種類型之一,這取決于其包含的數(shù)據(jù)。JavaScript中有三種原始類型:布爾型、數(shù)值型和字符串類型(不區(qū)分整數(shù)和浮點(diǎn)數(shù)是JavaScript與大多數(shù)其他主流語言的一個不同之處)。此外,還有對象類型和包含可執(zhí)行代碼的函數(shù)類型,前者是一種復(fù)合數(shù)據(jù)類型(數(shù)組是一種特殊的對象,它包含著一批值的有序集合)。最后,還有空類型(null)和未定義類型(undefined)這兩種數(shù)據(jù)類型。原始數(shù)據(jù)類型按值傳送,而其他數(shù)據(jù)類型則按引用傳送。
與其他弱類型語言一樣,JavaScript中的變量可以根據(jù)所賦的值改變類型。原始類型之間也可以進(jìn)行類型轉(zhuǎn)換。toString可以把數(shù)值或布爾值轉(zhuǎn)為字符串。parseFloat和parseInt函數(shù)可以把字符串轉(zhuǎn)變?yōu)閿?shù)值。雙重“非”可以把字符串或數(shù)值轉(zhuǎn)變?yōu)椴紶栔担?b>var bool = !!num;
匿名函數(shù)最有趣的用途是用來創(chuàng)建閉包。閉包是一個受到保護(hù)的變量空間,由內(nèi)嵌函數(shù)生成。JavaScript具有函數(shù)級的作用域。這意味著定義在函數(shù)內(nèi)部的變量在函數(shù)外部不能被訪問。JavaScript的作用域又是詞法性質(zhì)的。這意味著函數(shù)運(yùn)行在定義它的作用域中,而不是在調(diào)用它的作用域中。把這兩個因素結(jié)合起來,就能通過把變量包裹在匿名函數(shù)中而對其加以保護(hù)。3.依賴于接口的設(shè)計模式
4.用命名規(guī)范區(qū)別私用成員下面列出的設(shè)計模式,尤其依賴接口:
工廠模式。對象工廠所創(chuàng)建的具體對象會因具體情況而異。使用接口可以確保所創(chuàng)建出來的這些對象可以互換使用。也就是說,對象工廠可以保證其生產(chǎn)出來的對象都實(shí)現(xiàn)了必需的方法。
組合模式。如果不用接口你就不可能用這個模式。組合模式的中心思想在于可以將對象群體與其組成對象同等對待。這是通過讓它們實(shí)現(xiàn)同樣的接口來做到的。如果不進(jìn)行某種形式的鴨式辨型或類型檢查,組合模式就會失去大部分作用。
裝飾者模式。裝飾者通過透明地為另一對象提供包裝而發(fā)揮作用。這是通過實(shí)現(xiàn)與另外那個對象完全相同的接口而做到的。對于外界而言,一個裝飾者和它所包裝的對象看不出有什么區(qū)別。
命令模式。代碼中所有的命令對象都要實(shí)現(xiàn)同一批方法。通過使用接口,你為執(zhí)行這些命令對象而創(chuàng)建的類可以不必知道這些對象具體是什么,只要知道它們都實(shí)現(xiàn)了正確的接口即可。
在一些方法和屬性的名稱前面加下劃線以示其私用性。下劃線的這種用法是一個眾所周知的命名規(guī)范,它表明一個屬性(或方法)僅供對象內(nèi)部使用,直接訪問它或設(shè)置它可能會導(dǎo)致意想不到的后果。這有助于防止程序員對它的無意使用,卻不能防止對它的有意使用。后一個目標(biāo)的實(shí)現(xiàn)需要有真正私用性的方法。5.作用域
下面這個示例說明了JavaScript中作用域的特點(diǎn):
function foo() { var a = 10; function bar() { a *= 2; } bar(); return a; }
在這個示例中,a定義在函數(shù)foo中,但函數(shù)bar可以訪問它,因為bar也定義在foo中。bar在執(zhí)行過程中將a設(shè)置為a乘以2。當(dāng)bar在foo中被調(diào)用時它能夠訪問a,這可以理解。但是如果bar是在foo外部被調(diào)用呢?
function foo() { var a = 10; function bar() { a *= 2; return a; } return bar; } var baz = foo(); console.log(baz());//20 console.log(baz());//40 console.log(baz());//80 var blat = foo(); console.log(blat());//20
在上述代碼中,所返回的對bar函數(shù)的引用被賦給變量baz。這個函數(shù)現(xiàn)在是在foo外部被調(diào)用,但它依然能夠訪問a。這是因為JavaScript的作用域是詞法性的。函數(shù)是運(yùn)行在定義它們的作用域中(本例中是foo內(nèi)部的作用域),而不是運(yùn)行在調(diào)用它們的作用域中。只要bar被定義在foo中,它就能訪問在foo中定義的所有變量,即使foo的執(zhí)行已經(jīng)結(jié)束。
這就是閉包的一個例子。在foo返回后,它的作用域被保存下來,但只有它返回的那個函數(shù)能夠訪問這個作用域。在前面的示例中,baz和blat各有這個作用域及a的一個副本,而且只有它們自己能對其進(jìn)行修改。返回一個內(nèi)嵌函數(shù)是創(chuàng)建閉包最常用的手段。
在門戶打開型對象創(chuàng)建模式中,所有方法都創(chuàng)建在原型對象中,因此不管派生多少對象實(shí)例,這些方法在內(nèi)存中只存在一份。而包含特權(quán)方法、私用成員的創(chuàng)建模式中,每生成一個新的對象示例都將為每一個私用方法和特權(quán)方法生成一個新的副本。這會比其他做法耗費(fèi)更多內(nèi)存,所以只宜用在需要真正的私用成員的場合。這種對象創(chuàng)建模式也不利于派生子類,因為所派生出的子類不能訪問超類的任何私用屬性或方法。相比之下,在大多數(shù)語言中,子類都能訪問超類的所有私有屬性和方法。故在JavaScript中用閉包實(shí)現(xiàn)私用成員導(dǎo)致的派生問題稱為“繼承破壞封裝”。7.靜態(tài)方法和屬性
前面所講的作用域和閉包的概念可用于創(chuàng)建靜態(tài)成員,包括公用和私用的。大多數(shù)方法和屬性所關(guān)聯(lián)的是類的實(shí)例,而靜態(tài)成員所關(guān)聯(lián)的則是類本身。換句話說,靜態(tài)成員是在累的層次上操作,而不是在實(shí)例的層次上操作。每個靜態(tài)成員都只有一份。稍后將會看到,靜態(tài)成員是直接通過類對象訪問的。
下面是添加了靜態(tài)屬性和方法的Book類:
var Book = (function () { //私有靜態(tài)變量 var numOfBooks = 0; //私有靜態(tài)方法 function checkIsbn(isbn) { } //返回一個構(gòu)造器 return function (newIsbn, newTitle, newAuthor) { //私有屬性 var isbn, title, author; //特權(quán)方法 this.getIsbn = function () { return isbn; }; this.setIsbn = function (newIsbn) { if (!checkIsbn(newIsbn)) { throw new Error("Book: Invalid ISBN."); } isbn = newIsbn; }; this.getTitle = function () { return title; }; this.setTitle = function (newTitle) { title = newTitle || "No title specified"; }; this.getAuthor = function () { return author; }; this.setAuthor = function (newAuthor) { author = newAuthor || "No author specified"; }; //Constructed code. numOfBooks++; if (numOfBooks > 50) { throw new Error("."); } this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); } })(); //公共靜態(tài)方法 Book.convertToTitleCase = function (inputString) { }; //公共非特權(quán)方法 Book.prototype = { display: function () { } };
這里的私用成員和特權(quán)成員仍然被聲明在構(gòu)造器中(分別使用var和this關(guān)鍵字)。但哪個構(gòu)造器卻從原來的普通函數(shù)變成了一個內(nèi)嵌函數(shù),并且被作為包含它的函數(shù)的返回值賦給變量Book。這就創(chuàng)建了一個閉包,你可以把靜態(tài)的私用成員聲明在里面。位于外層函數(shù)聲明之后的一對空括號很重要,其作用是一段代碼載入就立即執(zhí)行這個函數(shù)(而不是在調(diào)用Book構(gòu)造函數(shù)時)。這個函數(shù)的返回值是另一個函數(shù),它被賦給Book變量,Book因此成了一個構(gòu)造函數(shù)。在實(shí)例化Book時,所調(diào)用的是這個內(nèi)層函數(shù)。外層那個函數(shù)只是用于創(chuàng)建一個可以用來存放靜態(tài)私用成員的閉包。
8.私用變量模仿常量在本例中,checkIsbn被設(shè)計為靜態(tài)方法 ,原因是為Book的每個實(shí)例都生成這個方法的一個新副本毫無道理。此外還有一個靜態(tài)屬性numOfBooks,其作用在于跟蹤Book構(gòu)造器的總調(diào)用次數(shù)。本例利用這個屬性將Book實(shí)例的個數(shù)限制為不超過50個。
這些私用的靜態(tài)成員可以從構(gòu)造器內(nèi)部訪問,這意味著所有私用函數(shù)和特權(quán)函數(shù)都能訪問它們。與其他方法相比,它們有一個明顯的優(yōu)點(diǎn),那就是內(nèi)存中只會存放一份。因為其中那些靜態(tài)方法被聲明在構(gòu)造器之外,所以它們不是特權(quán)方法,不能訪問任何定義在構(gòu)造器中的私用屬性。定義在構(gòu)造器中的私用方法能夠調(diào)用那些私用靜態(tài)方法,反之則不然。要判斷一個私用方法是否應(yīng)該被設(shè)計為靜態(tài)方法,一條經(jīng)驗法則是看它是否需要訪問任何實(shí)例數(shù)據(jù)。如果它不需要,那么將其設(shè)計為靜態(tài)方法會更有效率(從內(nèi)存占用的意義上來講),因為它只會被創(chuàng)建一份。
創(chuàng)建公用的靜態(tài)成員則容易得多,只需直接將其作為構(gòu)造函數(shù)這個對象的屬性創(chuàng)建即可,前述代碼中的方法converToTitleCase就是一例。這實(shí)際上相當(dāng)于把構(gòu)造器作為命名空間來使用。
所有公用靜態(tài)方法如果作為獨(dú)立的函數(shù)來聲明其實(shí)也同樣簡單,但最好還是像這樣把相關(guān)行為集中在一起。這些方法用于與類這個整體相關(guān)的任務(wù),而不是與類的任一特定實(shí)例相關(guān)的任務(wù)。它們并不直接依賴于對象實(shí)例中包含的任何數(shù)據(jù)。
通過創(chuàng)建只有取值器而沒有賦值器的私用變量可以模仿常量。
var Class = (function () { var UPPER_BOUND = 100; //構(gòu)造器 var ctor = function (constructorArgument) { }; //靜態(tài)特權(quán)方法 ctor.getUPPER_BOUND = function () { return UPPER_BOUND; }; return ctor; })();9.封裝之弊
10.單體模式私用方法很難進(jìn)行單元測試。因為它們及其內(nèi)部變量都是私用的,所以在對象外部無法訪問到它們。這個問題沒有什么很好的應(yīng)對之策。你要么通過使用公用方法來提供訪問途徑(這樣一來就葬送了使用私有方法所帶來的大多數(shù)好處),要么設(shè)法在對象內(nèi)部定義并執(zhí)行所有單元測試。最好的解決辦法是只對公用方法進(jìn)行單元測試。這應(yīng)該能覆蓋到所有私用方法,盡管對它們的測試只是間接的。這種問題不是JavaScript所獨(dú)有的,只對公用方法進(jìn)行單元測試是一種廣為接收的處理方式。
使用封裝意味著不得不與復(fù)雜的作用域鏈打交道。
封裝可能會損害類的靈活性,致使其無法被用于某些你未曾想到過的目的。
單體模式是JavaScript中最基本但又最有用的模式之一,它可能比其他任何模式都更常用。這種模式提供了一種將代碼組織為一個邏輯單元的手段,這個邏輯單元中的代碼可以通過單一的變量進(jìn)行訪問。通過確保單體對象只存在一份實(shí)例,你就可以確信自己的所有代碼使用的都是同樣的全局資源。11.單體的基本結(jié)構(gòu)
單體類在JavaScript中有許多用處。它們可以用來劃分命名空間,以減少網(wǎng)頁中全局變量的數(shù)目。更重要的是,借助于單體模式,你可以把代碼組織得更為一致,從而使其更容易閱讀和維護(hù)。
var Singleton = { attribute1: true, attribute2: 10, method1: function () { }, method2: function (args) { } };
這個單體對象可以被修改。你可以為其添加新成員,這一點(diǎn)與別的對象字面量沒有什么不同。你也可以用delete運(yùn)算符刪除其現(xiàn)有成員。這實(shí)際上違背了面向?qū)ο笤O(shè)計的一條原則:類可以被擴(kuò)展,但不應(yīng)該被修改。
按傳統(tǒng)的定義,單體是一個只能被實(shí)例化一次并且可以通過一個眾所周知的訪問點(diǎn)訪問的類。要是嚴(yán)格按照這個定義來說,前面的例子所示的并不是一個單體,因為它不是一個可實(shí)例化的類。我們打算把單體模式定義的更廣義一些:單體是一個用來劃分命名空間并將一批相關(guān)方法和屬性組織在一起的對象,如果可以被實(shí)例化,那么它只能被實(shí)例化一次。
12.劃分命名空間為了避免無意中改寫變量,最好的解決辦法之一是用單體對象將代碼組織在命名空間之中。下面是前面的例子用單體模式改良后的結(jié)果:
var MyNamespace = { findProduct: function (id) { } };
現(xiàn)在findProduct函數(shù)是MyNamespace中的一個方法,它不會被全局命名空間中聲明的任何新變量改寫。要注意,該方法仍然可以從各個地方訪問。不同之處在于現(xiàn)在其調(diào)用方式不是findProduct(id),而是MyNamespace.findProduct(id)。還有一個好處就是,這可以讓其他程序員大體知道這個方法的聲明地點(diǎn)及其作用。用命名空間把類似的方法組織到一起,也有助于增強(qiáng)代碼的文檔性。
13.模塊模式有一種單體模式被稱為模塊模式,因為它可以把一批相關(guān)方法和屬性組織為模塊并起到劃分命名空間的作用。例如:
MyNamespace.Singleton = (function () { //私有成員 var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { } function privateMethod2() { } return { //public members publicAttribute1: true, publicAttribute2: 10, publicMethod1: function () { }, publicMethod2: function (args) { } } })();14.簡單工廠模式
最好用一個例子來說明簡單工廠模式的概念。假設(shè)你想開幾個自行車商店,每個店都有幾種型號的自行車出售。這可以用一個類來表示:
/*BicycleShop class.*/ var BicycleShop = function () { }; BicycleShop.prototype = { sellBicycle: function (model) { var bicycle; switch (model) { case "The Speedster": bicycle = new SpeedSter(); break; case "The Lowrider": bicycle = new Lowrider(); break; case "The Comfort Cruiser": default: bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); bicycle.assemble(); bicycle.wash(); return bicycle; } };
sellBicycle方法根據(jù)所要求的自行車型號用switch語句創(chuàng)建一個自行車的實(shí)例。各種型號的自行車實(shí)例可以互換使用,因為它們都實(shí)現(xiàn)了Bicycle接口:
/* The Bicycle interface. */ var Bicycle = new Interface("Bicycle", ["assemble", "wash", "ride", "repair"]); /* Speedster class. */ var Speedster = function () { }; Speedster.prototype = { assemble: function () { }, wash: function () { }, ride: function () { }, repair: function () { } };
要出售某種型號的自行車,只要調(diào)用sellBicycle方法即可:
var californiaCruisers = new BicycleShop(); var yourNewBike = californiaCruisers.sellBicycle("The Speedster");
在情況發(fā)生變化之前,這倒也挺管用。但要是你想在供貨目錄中加入一款新車型又會怎么樣呢?你得為此修改BicycleShop的代碼,哪怕這個類的實(shí)際功能實(shí)際上并沒有發(fā)生改變——依舊是創(chuàng)建一個自行車的新實(shí)例,組裝它,清洗它,然后把它交給顧客。更好的解決辦法是把sellBicycle方法中“創(chuàng)建新實(shí)例”這部分工作轉(zhuǎn)交給一個簡單工廠對象:
/* BicycleFactory namespace. */ var BicycleFactory = { createBicycle:function(model){ var bicycle; switch (model) { case "The Speedster": bicycle = new SpeedSter(); break; case "The Lowrider": bicycle = new Lowrider(); break; case "The Comfort Cruiser": default: bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); return bicycle; } };
BicycleFactory是一個單體,用來把createBicycle方法封裝在一個命名空間中。這個方法返回一個實(shí)現(xiàn)了Bicycle接口的對象,然后你可以照常對其進(jìn)行組裝和清洗:
/* BicycleShop class, improved. */ var BicycleShop = function () { }; BicycleShop.prototype = { sellBicycle: function (model) { var bicycle = BicycleFactory.createBicycle(model); bicycle.assemble(); bicycle.wash(); return bicycle; } };
這個BicycleFactory對象可以供各種類用來創(chuàng)建新的自行車實(shí)例。有關(guān)可供車型的所有信息集中在一個地方管理 ,所以添加更多車型很容易:
/* BicycleFactory namespace,with more models. */ var BicycleFactory = { createBicycle: function (model) { var bicycle; switch (model) { case "The Speedster": bicycle = new SpeedSter(); break; case "The Lowrider": bicycle = new Lowrider(); break; case "The Flatlander": bicycle = new Flatlander(); break; case "The Comfort Cruiser": default: bicycle = new ComfortCruiser(); } Interface.ensureImplements(bicycle, Bicycle); return bicycle; } };15.工廠模式
真正的工廠模式與簡單工廠模式的區(qū)別在于,它不是另外使用一個類或?qū)ο髞韯?chuàng)建自行車,而是使用一個子類。按照正式定義,工廠是一個將其成員對象的實(shí)例化推遲到子類中進(jìn)行的類。16.工廠模式的適用場合
17.工廠模式之利動態(tài)實(shí)現(xiàn):如果需要創(chuàng)建一些用不同方式實(shí)現(xiàn)同一接口的對象,那么可以使用一個工廠方法或簡單工廠對象來簡化選擇實(shí)現(xiàn)的過程。
節(jié)省設(shè)置開銷:如果對象需要進(jìn)行復(fù)雜并且彼此相關(guān)的設(shè)置,那么使用工廠模式可以減少每種對象所需的代碼量。如果這種設(shè)置只需要為特定類型的所有實(shí)例執(zhí)行一次即可,這種作用尤為突出。把這種設(shè)置代碼放到類的構(gòu)造函數(shù)中并不是一種高效的做法,這是因為即便設(shè)置工作已經(jīng)完成,每次創(chuàng)建新實(shí)例的時候這些代碼還是會執(zhí)行,而且這樣做會把設(shè)置代碼分散到不同的類中。工廠方法非常適合于這種場合。它可以在實(shí)例化所有需要的對象之前先一次性地進(jìn)行設(shè)置。無論有多少類會被實(shí)例化,這種辦法都可以讓設(shè)置代碼集中在一個地方。
用許多小型對象組成一個大對象
18.橋接模式工廠模式的主要好處在于消除對象間的耦合。通過使用工廠方法而不是new關(guān)鍵字及具體類,你可以把所有實(shí)例化的代碼集中在一個位置。這可以大大簡化更換所用的類或在運(yùn)行期間動態(tài)選擇所用的類的工作。在派生子類時它也提供了更強(qiáng)大的靈活性。
所有這些好處都與面向?qū)ο笤O(shè)計的這兩條原則有關(guān):弱化對象間的耦合;防止代碼的重復(fù)。在一個方法中進(jìn)行類的實(shí)例化,可以消除重復(fù)性的代碼。這是在用一個對接口的調(diào)用取代一個具體的實(shí)現(xiàn)。這些都有助于創(chuàng)建模塊化的代碼。
橋接模式最常見和實(shí)際的應(yīng)用場合之一就是事件監(jiān)聽器回調(diào)函數(shù)。假設(shè)有一個名為getBeerById的API函數(shù),它根據(jù)一個標(biāo)識符返回有關(guān)某種啤酒的信息。你希望用戶在點(diǎn)擊的時候獲取這種信息。那個被點(diǎn)擊的元素很可能有啤酒的標(biāo)識符信息,它可能是作為元素自身的ID保存,也可能是作為別的自定義屬性保存。下面是一種做法:
addEvent(element, "click", getBeerById); function getBeerById(e) { var id = this.id; asyncRequest("GET", "beer.uri?id=" + id, function (resp) { console.log(resp.responseText); }); }
這個API只能工作在瀏覽器中,如果要對這個API函數(shù)做單元測試,或者在命令行中執(zhí)行,可能會報錯。一個優(yōu)良的API設(shè)計,不應(yīng)該把它與任何特定的實(shí)現(xiàn)攪在一起。
function getBeerById(id, callback) { asyncRequest("GET", "beer.uri?id=" + id, function (resp) { callback(resp.responseText); }) }
現(xiàn)在我們將針對接口而不是實(shí)現(xiàn)進(jìn)行編程,用橋接模式把抽象隔離開來:
addEvent(element, "click", getBeerByIdBridge); function getBeerBIdBridge(e) { getBeerById(this.id, function (beer) { console.log(beer); }); }
這下getBeerById并沒有和事件對象捆綁在一起了。
19.用橋接模式聯(lián)結(jié)多個類var Class1 = function (a, b, c) { this.a = a; this.b = b; this.c = c; }; var Class2 = function (d) { this.d = d; }; var BridgeClass = function (a, b, c, d) { this.one = new Class1(a, b, c); this.two = new Class2(d); };20.適配器模式
適配器模式可以用來在現(xiàn)有接口和不兼容的類之間進(jìn)行適配。使用這種模式的對象又叫包裝器,因為它們是在用一個新的接口包裝另一個對象。
21.適配器的特點(diǎn)適配器可以被添加到現(xiàn)有代碼中以協(xié)調(diào)兩個不同的接口。如果現(xiàn)有代碼的接口能很好地滿足需要,那就可能沒有必要使用適配器。
從表面上看,適配器模式很像門面模式。它們都要對別的對象進(jìn)行包裝并改變其呈現(xiàn)的接口。二者的差別在于它們?nèi)绾胃淖兘涌凇iT面元素展現(xiàn)的是一個簡化的接口,它并不提供額外的選擇,而且有時為了方便完成某些常見任務(wù)它還會做出一些假定。而適配器則要把一個接口轉(zhuǎn)換為另一個接口,它并不會濾除某些能力,也不會簡化接口。如果客戶系統(tǒng)期待的API不可用,那就需要用到適配器。
適配器可被實(shí)現(xiàn)為不兼容的方法調(diào)用之間的一個代碼薄層。
示例:
假如你有一個對象還有一個以三個字符串為參數(shù)的函數(shù):
var clientObject = { string1: "foo", string2: "bar", string3: "baz" }; function interfaceMethod(str1, str2, str3) { }
為了把clientObject作為參數(shù)傳遞給interfaceMethod,需要用到適配器。我們可以這樣創(chuàng)建一個:
function clientToInterfaceAdapter(o) { interfaceMethod(o.string1, o.string2, o.string3); } //現(xiàn)在就可以把整個對象傳給這個函數(shù) clientToInterfaceAdapter(clientObject);
clientToInterfaceAdapter函數(shù)的作用就在于對interfaceMethod函數(shù)進(jìn)行包裝,并把傳遞給它的參數(shù)轉(zhuǎn)換給后者需要的形式。
22.裝飾者模式裝飾者模式可用來透明地把對象包裝在具有同樣接口的另一對象中。這樣一來,你可以給一個方法添加一些行為,然后將方法調(diào)用傳遞給原始對象。相對于創(chuàng)建子類來說,使用裝飾者對象是一種更靈活的選擇。23.享元模式
享元模式最適合于解決因創(chuàng)建大量類似對象而累及的性能問題。這種模式在JavaScript中尤其有用,因為復(fù)雜的JavaScript代碼可能很快就會用光瀏覽器的所有可用內(nèi)存。通過把大量獨(dú)立對象轉(zhuǎn)化為少量共享對象,可以降低運(yùn)行Web應(yīng)用程序所需的資源數(shù)量。
享元模式用于減少應(yīng)用程序所需對象的數(shù)量。這是通過將對象的內(nèi)部狀態(tài)劃分為內(nèi)在數(shù)據(jù)和外在數(shù)據(jù)兩類而實(shí)現(xiàn)的。內(nèi)在數(shù)據(jù)是指類的內(nèi)部方法所需的信息,沒有這種數(shù)據(jù)的話類不能正常運(yùn)轉(zhuǎn)。外在數(shù)據(jù)則是可以從類身上剝離并存儲在其外部的信息。我們可以將內(nèi)在狀態(tài)相同的所有對象替換為同一個共享對象,這種方法可以把對象數(shù)量減少到不同內(nèi)在狀態(tài)的數(shù)量。
24.實(shí)現(xiàn)享元模式的一般步驟將所有外在數(shù)據(jù)從目標(biāo)剝離。具體做法是盡可能多地刪除該類的屬性,所刪除的應(yīng)該是那種因?qū)嵗惖膶傩浴?gòu)造函數(shù)的參數(shù)也要這樣處理。這些參數(shù)應(yīng)該被添加到該類的各個方法。這些外在數(shù)據(jù)現(xiàn)在不再保存在類的內(nèi)部,而是由管理器提供給類的方法。經(jīng)過這樣的處理后,目標(biāo)類應(yīng)該依然具有與之前一樣的功能。唯一的區(qū)別在于數(shù)據(jù)的來源發(fā)生了變化。
創(chuàng)建一個用來控制該類的實(shí)例化的工廠。這個工廠應(yīng)該掌握該類所有已創(chuàng)建出來的獨(dú)一無二的實(shí)例。其具體做法之一是用一個對象字面量來保存每一個這類對象的引用,并以用來生成這些對象的參數(shù)的唯一性組合作為它們的索引。這樣一來,每次要求工廠提供一個對象時,它會先檢查那個對象字面量,看看以前是否請求過這個對象。如果是,那么只要返回那個現(xiàn)有對象的引用就行。否則它會創(chuàng)建一個新對象并將其引用保存在那個對象字面量中,然后返回這個對象。另一種做法稱為對象池,這種技術(shù)用數(shù)組來保存所創(chuàng)建的對象的引用。它適合于注重可用對象的數(shù)量而不是那些多帶帶配置的實(shí)例的場合。這種技術(shù)可用來將所實(shí)例化的對象的數(shù)目維持在最低值。工廠會處理根據(jù)內(nèi)在數(shù)據(jù)創(chuàng)建對象的所有事宜。
創(chuàng)建一個用來保存外在數(shù)據(jù)的管理器。該管理器對象負(fù)責(zé)控制處理外在數(shù)據(jù)的種種事宜。在實(shí)施優(yōu)化之前,要是需要一個目標(biāo)類的實(shí)例,你會把所有數(shù)據(jù)傳給構(gòu)造函數(shù)以創(chuàng)建其新實(shí)例。而現(xiàn)在要是需要一個實(shí)例,你會調(diào)用管理器的某個方法,把所有數(shù)據(jù)都提供給它。這個方法會分辨內(nèi)在數(shù)據(jù)和外在數(shù)據(jù)。它把內(nèi)在數(shù)據(jù)提供給工廠對象以創(chuàng)建一個對象(或者,如果已經(jīng)存在這樣一個對象的話,則重用該對象)。外在數(shù)據(jù)則被保存在管理器內(nèi)的一個數(shù)據(jù)結(jié)構(gòu)中。管理器隨后會根據(jù)需要將這些數(shù)據(jù)提供給共享對象的方法,其效果就如同該類有許多實(shí)例一樣。
25.觀察者模式在事件驅(qū)動的環(huán)境中,比如瀏覽器這種持續(xù)尋求用戶關(guān)注的環(huán)境中,觀察者模式(又名發(fā)布者-訂閱者模式)是一種管理人與其任務(wù)之間的關(guān)系(確切的說,是對象及其行為和狀態(tài)之間的關(guān)系)的得力工具。
觀察者模式中存在兩個角色:觀察者和被觀察者。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79986.html
摘要:設(shè)計模式是以面向?qū)ο缶幊虨榛A(chǔ)的,的面向?qū)ο缶幊毯蛡鹘y(tǒng)的的面向?qū)ο缶幊逃行┎顒e,這讓我一開始接觸的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解設(shè)計模式必須要先搞懂面向?qū)ο缶幊蹋駝t只會讓你自己更痛苦。 JavaScript 中的構(gòu)造函數(shù) 學(xué)習(xí)總結(jié)。知識只有分享才有存在的意義。 是時候替換你的 for 循環(huán)大法了~ 《小分享》JavaScript中數(shù)組的那些迭代方法~ ...
摘要:請記住,這些書中的一些可能不是最新的,但概念和基礎(chǔ)仍應(yīng)適用。是最好的老師之一。的秘密由部分組成。在你完成這些書后,查看書籍和最好的本土?xí)? 我看過三本,第1本,第二本,第四本。第一本買的的實(shí)體書,其他兩本看的是電子書。第一本是大名鼎鼎老道寫的,書很薄,但是非常經(jīng)典。javascirpt忍者秘籍是jquery的作者寫的,也是非常經(jīng)典。you dont kown js系列也是非常好。看了...
摘要:是文檔的一種表示結(jié)構(gòu)。這些任務(wù)大部分都是基于它。這個實(shí)踐的重點(diǎn)是把你在前端練級攻略第部分中學(xué)到的一些東西和結(jié)合起來。一旦你進(jìn)入框架部分,你將更好地理解并使用它們。到目前為止,你一直在使用進(jìn)行操作。它是在前端系統(tǒng)像今天這樣復(fù)雜之前編寫的。 本文是 前端練級攻略 第二部分,第一部分請看下面: 前端練級攻略(第一部分) 在第二部分,我們將重點(diǎn)學(xué)習(xí) JavaScript 作為一種獨(dú)立的語言,如...
摘要:首先,需要來理清一些基礎(chǔ)的計算機(jī)編程概念編程哲學(xué)與設(shè)計模式計算機(jī)編程理念源自于對現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過程式和函數(shù)式編程。 JavaScript 中的原型機(jī)制一直以來都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因為絕大多數(shù)人沒有想要深刻理解這個機(jī)制的內(nèi)涵,以及越來越多的開發(fā)者缺乏計算機(jī)編程相關(guān)的基礎(chǔ)知識。對于這樣的開發(fā)者來說 J...
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠矶际侵械闹鲗?dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠矶际荍avaScript中的主導(dǎo)范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數(shù)式編程越來越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:前端入門的門檻相對較低,學(xué)習(xí)曲線是越來越陡峭,由淺入深,可以分為四個階段。第二階段高級程序設(shè)計有的書是用來成為經(jīng)典的,比如犀牛書還有些書是用來超越經(jīng)典的,顯然這本書就是。接下來可以看看教程,看看源代碼,嘗試著寫一寫這些效果。 前端入門的門檻相對較低,學(xué)習(xí)曲線是越來越陡峭,由淺入深,可以分為四個階段。 第一階段:《JavaScript DOM編程藝術(shù)》 看這本書之前,請先確認(rèn)你對J...
閱讀 1572·2021-10-14 09:42
閱讀 3815·2021-09-07 09:59
閱讀 1292·2019-08-30 15:55
閱讀 572·2019-08-30 11:17
閱讀 3337·2019-08-29 16:06
閱讀 500·2019-08-29 14:06
閱讀 3123·2019-08-28 18:14
閱讀 3642·2019-08-26 13:55