摘要:?jiǎn)误w模式在多種設(shè)計(jì)模式中,單體模式是最簡(jiǎn)單,也是最基礎(chǔ)的設(shè)計(jì)模式。和之前說(shuō)到的下劃線表示私用成員方法比較起來(lái),最大的優(yōu)點(diǎn)就是可以創(chuàng)建真正的私用成員,使其不會(huì)在構(gòu)造函數(shù)之外被隨意修改。
單體模式
在多種Javascript設(shè)計(jì)模式中,單體模式是最簡(jiǎn)單,也是最基礎(chǔ)的設(shè)計(jì)模式。它基礎(chǔ)到似乎不太像是一種設(shè)計(jì)模式,因?yàn)槲覀冊(cè)诰帉?xiě)代碼的過(guò)程中隨時(shí)都會(huì)用到,并不需要過(guò)多思考,這是它簡(jiǎn)單的一面。同時(shí),它不僅可以多帶帶存在,甚至也可以成為其他較高級(jí)設(shè)計(jì)模式的組成部分,這也是為什么說(shuō)它基礎(chǔ)的原因。
基本結(jié)構(gòu)既然說(shuō)了單體模式是非常簡(jiǎn)單的,它的結(jié)構(gòu)也是很簡(jiǎn)單的。最簡(jiǎn)單的單體結(jié)構(gòu)實(shí)際上就是一個(gè)對(duì)象字面量:
var Singleton = { attribute1: true, attribute2: 1, method1: function() { ... }, method2: function() { ... } }
這就是一個(gè)基本的單體結(jié)構(gòu)了。
但是,不是任何對(duì)象字面量都可以被稱(chēng)作為單體結(jié)構(gòu)的,單體結(jié)構(gòu)應(yīng)該是一個(gè)只能被實(shí)例化一次,并且可以通過(guò)一個(gè)訪問(wèn)點(diǎn)訪問(wèn)的類(lèi)。所謂訪問(wèn)點(diǎn),可以理解為一個(gè)變量,這個(gè)變量在全局范圍內(nèi)可以訪問(wèn)到,并且只有一個(gè)。
單體結(jié)構(gòu)的作用那么單體結(jié)構(gòu)的作用是什么呢,難道只是用來(lái)創(chuàng)建一個(gè)實(shí)例化的對(duì)象這么簡(jiǎn)單嗎?
命名空間
當(dāng)然不是的,單體最顯而易見(jiàn)的作用就是劃分命名空間。單體結(jié)構(gòu)在頁(yè)面中有一個(gè)訪問(wèn)點(diǎn),那么單體中保存的所有屬性和方法也就可以從這個(gè)訪問(wèn)點(diǎn)訪問(wèn)了,通過(guò)點(diǎn)運(yùn)算符的形式。而且也只有通過(guò)訪問(wèn)點(diǎn)才可以訪問(wèn)到。Javascript中的所有變量都是可以被改寫(xiě)的,當(dāng)一個(gè)程序員維護(hù)多個(gè)變量的時(shí)候,如果不將他們歸類(lèi)到命名空間中去的話,一旦變量被修改,查找起來(lái)將非常麻煩。同時(shí),一個(gè)命名良好的命名空間名稱(chēng)也可以提醒其他的程序員不要隨便修改其中的變量。
var Classicemi = { setName: function(name) { ... }, // 其他方法 }
在其他地方訪問(wèn)setName方法的時(shí)候,一定要通過(guò)Classicemi.setName才能訪問(wèn)的到,這可以提醒其他程序員這個(gè)方法的作用和聲明的地點(diǎn)。通過(guò)命名空間將相似的方法組合到一起也可以增加代碼的文檔性。另一方面,網(wǎng)頁(yè)上的Javascript代碼會(huì)根據(jù)其用途有不同的劃分,分不同的人來(lái)維護(hù)。例如JS庫(kù)代碼,廣告代碼等。為了避免彼此之間產(chǎn)生沖突,在全局對(duì)象中也可以給不同用途的代碼劃分各自的命名空間,也就是存到各個(gè)單體中。
var Classicemi = {}; Classicemi.Common = { ... }; Classicemi.ErrorCodes = { ... };
網(wǎng)頁(yè)專(zhuān)用代碼包裝器
這是單體常見(jiàn)用法的一個(gè)示例。
在一個(gè)網(wǎng)站中,有些Javascript代碼是整個(gè)網(wǎng)站都要用到的,比如框架,庫(kù)等。而有些代碼是特定的網(wǎng)頁(yè)才會(huì)用到,例如對(duì)一個(gè)頁(yè)面中的DOM元素添加事件監(jiān)聽(tīng)等。一般我們會(huì)通過(guò)給頁(yè)面的load事件創(chuàng)建一個(gè)init方法來(lái)對(duì)所有需要的操作進(jìn)行初始化,將所有的初始化代碼放在一個(gè)方法中。
比如含有一個(gè)表單的頁(yè)面,我們要取消submit的默認(rèn)行為并添加ajax交互。
Classicemi.RegPage = { FORM_ID: "reg-form", OUTPUT_ID: "reg-results", // 表單處理方法 handleSubmit: function(e) { e.preventDefult(); ... } , sendRegistration: function(data) { ... // 發(fā)送XHR請(qǐng)求 }, ... // 初始化方法 init: function() { Classicemi.RegPage.formEl = $(Classicemi.RegPage.FORM_ID); Classicemi.RegPage.outputEl = $(Classicemi.RegPage.OUTPUT_ID); addEvent(Classicemi.RegPage.FormEl, "submit", Classicemi.RegPage.handleSubmit); // 添加事件 } }; // 頁(yè)面加載后運(yùn)行初始化方法 addLoadEvent(Classicemi.PageName.init);
這樣處理之后,對(duì)于不支持XHR的老式瀏覽器,可以按照原有方式發(fā)送表單數(shù)據(jù)并刷新頁(yè)面。而現(xiàn)代瀏覽器中則可以阻止表單提交的默認(rèn)行為,改由ajax對(duì)頁(yè)面進(jìn)行部分刷新,提供更好的用戶體驗(yàn)。
在單體中表示私用成員對(duì)象中有時(shí)候有些屬性和方法是需要進(jìn)行保護(hù),避免被修改的,這些成員稱(chēng)為私用成員。在單體中聲明私用成員也是保護(hù)變量的一個(gè)好方法,另外,單體中創(chuàng)建私用成員的另一個(gè)好處在于由于單體只會(huì)被實(shí)例化一次,定義私用成員的時(shí)候就不用過(guò)多考慮內(nèi)存浪費(fèi)的問(wèn)題。
偽私用成員(下劃線表示法)
通過(guò)特殊命名的變量來(lái)提醒其他開(kāi)發(fā)者不要直接訪問(wèn)對(duì)象成員的方法。
Classicemi.Singleton = { // 私用成員 _privateMethod: function() { ... }, // 公開(kāi)成員 publicMethod: function() { ... } }
在該單體的方法中,可以通過(guò)this訪問(wèn)其他方法,但這會(huì)有一定的風(fēng)險(xiǎn),因?yàn)樵谔厥馇闆r下this不一定指向該單體。因此還是將調(diào)用名稱(chēng)寫(xiě)全是最安全的做法。
使用閉包
加下劃線的方法畢竟是假的,使用閉包才能創(chuàng)建真正意義上的私用成員。我們知道Javascript只存在函數(shù)作用域,因此要利用閉包的特性就不能使用對(duì)象字面量的形式,而要通過(guò)構(gòu)造函數(shù)返回來(lái)實(shí)現(xiàn)單體對(duì)象的創(chuàng)建了。第一步,我們通過(guò)一個(gè)構(gòu)造函數(shù)返回一個(gè)空對(duì)象,這就是單體對(duì)象的初始化:
var Classicemi.Singleton = (function() { return {}; })();
我們通過(guò)一個(gè)自執(zhí)行構(gòu)造函數(shù)返回單體對(duì)象的實(shí)例,下面就可以在這個(gè)構(gòu)造函數(shù)中添加我們需要的私用對(duì)象了。
var Classicemi.Singleton = (function() { // 私用屬性 var privateAttribute = true; // 私用方法 function privateMethod() { ... } return {}; })();
可以公開(kāi)訪問(wèn)的公開(kāi)屬性和方法可以寫(xiě)在構(gòu)造函數(shù)返回的對(duì)象中:
var Classicemi.Singleton = (function() { // 私用屬性 var privateAttribute = true; // 私用方法 function privateMethod() { ... } return { publicAttribute: false, publicMethod: function() { ... } }; })();
這就是用閉包創(chuàng)建私有成員的方法,這種單體模式又被成為模塊模式(Module Pattern),我們創(chuàng)建的單體可以作為模塊,對(duì)代碼進(jìn)行組織,并劃分命名空間。
和之前說(shuō)到的下劃線表示私用成員方法比較起來(lái),最大的優(yōu)點(diǎn)就是可以創(chuàng)建真正的私用成員,使其不會(huì)在構(gòu)造函數(shù)之外被隨意修改。同時(shí),由于單體只會(huì)被實(shí)例化一次,不用擔(dān)心內(nèi)存浪費(fèi)的問(wèn)題。單體模式是Javascript中最簡(jiǎn)單,最流行的模式之一。
惰性實(shí)例化單體單體一般會(huì)在頁(yè)面加載過(guò)程中進(jìn)行實(shí)例化,如果單體的體積比較大的話,可能會(huì)對(duì)加載速度造成影響。對(duì)于體積比較大,在頁(yè)面加載時(shí)也暫時(shí)不會(huì)起作用的單體,我們可以通過(guò)惰性加載(lazy loading)的方式進(jìn)行實(shí)例化,也就是在需要的時(shí)候再進(jìn)行實(shí)例化。
要實(shí)現(xiàn)惰性加載,我們要借助一個(gè)靜態(tài)方法來(lái)實(shí)現(xiàn)。在單體的命名空間中,我們聲明這樣一個(gè)方法getInstance()。這個(gè)方法會(huì)對(duì)單體是否已經(jīng)進(jìn)行了實(shí)例化進(jìn)行檢測(cè),如果還沒(méi)有實(shí)例,則會(huì)創(chuàng)建并返回實(shí)例。如果已經(jīng)實(shí)例化過(guò)了,則會(huì)返回現(xiàn)有實(shí)例。
實(shí)現(xiàn)惰性加載,我們要把原單體構(gòu)造函數(shù)中的所有成員轉(zhuǎn)移到一個(gè)內(nèi)部的新構(gòu)造函數(shù)中去:
Classicemi.Singleton = (function() { function constructor() { // 私用屬性 var privateAttribute = true; // 私用方法 function privateMethod() { ... } return { publicAttribute: false, publicMethod: function() { ... } }; } })();
這個(gè)內(nèi)嵌構(gòu)造函數(shù)不能從閉包外部訪問(wèn),那么在閉包內(nèi)部返回對(duì)象中的getInstance方法可以有訪問(wèn)constructor方法的特權(quán),可以保證constructor方法只會(huì)被我們控制。
在getInstance()方法內(nèi)部,首先要對(duì)單體是否已經(jīng)實(shí)例化進(jìn)行檢查,如果已經(jīng)實(shí)例化過(guò),就將其返回。如果沒(méi)有實(shí)例化,就調(diào)用constructor方法。我們需要一個(gè)變量來(lái)保存實(shí)例化后的單體。
Classicemi.Singleton = (function() { var uniqueInstance; // 保存實(shí)例化后的單體 function constructor() { ... } return { getInstance: function() { if (!uniqueInstance) { uniqueInstance = constructor(); } return uniqueInstance; } } })();
單體的構(gòu)造函數(shù)像這樣被改寫(xiě)后,調(diào)用其方法的代碼就要由這樣:
Classicemi.Singleton.publicMethod();
改寫(xiě)為:
Classicemi.Singleton.getInstance().publicMethod();
惰性加載的使用可以避免不必要的單體在頁(yè)面加載時(shí)實(shí)例化影響加載速度,但引入一個(gè)getInstance()方法也會(huì)在一定程度上增加代碼的復(fù)雜性,因此惰性加載應(yīng)該在必要的時(shí)候再使用。
分支分支(branching)技術(shù)的意義在于根據(jù)不同的條件,對(duì)單體進(jìn)行不同的實(shí)例化過(guò)程。
constructor │condition ┌──────────────┼─────────────┐ │ │ │ return branch1 branch2 branch3
在構(gòu)造函數(shù)中存在不同的實(shí)例對(duì)象,針對(duì)condition判斷條件的不同返回值,構(gòu)造函數(shù)返回不同的對(duì)象作為單體的實(shí)例。例如對(duì)不同的瀏覽器來(lái)說(shuō),支持的XHR對(duì)象不一樣,大多數(shù)瀏覽器中是XMLHttpRequest的實(shí)例,早期的IE瀏覽器中是某種ActiveX的實(shí)例。我們?cè)趧?chuàng)建XHR對(duì)象的時(shí)候,可以根據(jù)不同瀏覽器的支持情況返回不同的實(shí)例,like this:
var XHRFactory = (function() { var standard = { createXHR: function() { return new XMLHttpRequest(); } }; var activeX = { createXHR: function() { return new ActiveXObject("Msxml2.XMLHTTP"); } }; var activeOld = { createXHR: function() { return new ActiveXObject("Microsofe.XMLHTTP"); } } var testObj; try { testObj = standard.createXHR(); return standard; } catch (e) { try { testObj = activeX.createXHR(); return standard; } catch (e) { try { testObj = activeOld.createXHR(); return standard; } catch (e) { throw new Error("No XHR object found in this environment."); } } } })();
通過(guò)try-catch語(yǔ)句對(duì)瀏覽器XHR的支持性進(jìn)行測(cè)試同時(shí)防止拋出錯(cuò)誤,這樣不同瀏覽器都能創(chuàng)建出自己支持的XHR對(duì)象的實(shí)例。
單體模式之利弊 單體模式之利單體模式能很好的組織代碼,由于單體對(duì)象只會(huì)實(shí)例化一次,單體對(duì)象中的代碼可以方便地進(jìn)行維護(hù)。
單體模式可以生成自己的命名空間,防止自己的代碼被別人隨意修改。
惰性實(shí)例化,有助于性能的提升。
分支,針對(duì)特定環(huán)境定制專(zhuān)屬的方法。
單體模式之弊類(lèi)之間的耦合性可能增強(qiáng),因?yàn)橐ㄟ^(guò)命名空間去對(duì)一些方法進(jìn)行訪問(wèn),強(qiáng)耦合的后果會(huì)不利于單元測(cè)試。
鏈?zhǔn)秸{(diào)用說(shuō)起鏈?zhǔn)秸{(diào)用,絕大多數(shù)的前端開(kāi)發(fā)者一定會(huì)馬上想到大名鼎鼎的jQuery,這說(shuō)明jQuery對(duì)開(kāi)發(fā)者思想的束縛還真是深啊。。。
Anyway,jQuery的鏈?zhǔn)秸{(diào)用特性確實(shí)是給開(kāi)發(fā)帶來(lái)了很多的便利,一條語(yǔ)句可以完成幾條語(yǔ)句的工作。那么鏈?zhǔn)秸{(diào)用是怎么實(shí)現(xiàn)的呢?
要實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用其實(shí)是利用JavaScript的一些語(yǔ)法特性,主要分為兩個(gè)部分:
1. 創(chuàng)建包含需要操作的HTML元素的對(duì)象。
2. 對(duì)這個(gè)HTML元素進(jìn)行操作的方法。
將所有的方法都定義在構(gòu)造器函數(shù)prototype屬性所指的對(duì)象中,這樣所有的實(shí)例都可以調(diào)用這些方法,并且所有的方法都返回調(diào)用它們的實(shí)例的引用。這樣就實(shí)現(xiàn)了一個(gè)基本的鏈?zhǔn)秸{(diào)用。
(function() { function _$(els) { this.elements = []; ... // 通過(guò)一系列操作將匹配元素存入this.elements } window.$ = function() { // 對(duì)外接口 return new _$(arguments); } })();
接下來(lái)就可以在構(gòu)造器函數(shù)的原型所指對(duì)象中添加我們需要的方法了,我們可以根據(jù)需要添加DOM方法,ajax方法等,然后就可以完成一個(gè)小JS庫(kù)了~
(function() { function _$(els) { ... } _$.prototype = { each: function(fn) { for (var i = 0, len = this.length; i < len; i++) { fn.call(this, this.elements[i]); } return this; } ... } })();
關(guān)鍵的一點(diǎn)就是每個(gè)方法的最后都是return this;,它返回調(diào)用方法的實(shí)例引用,這樣我們可以繼續(xù)讓這個(gè)this去調(diào)用其他方法,從而實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。
使用回調(diào)
回調(diào)的模式如果按照常規(guī)的方式運(yùn)用在一些取值器方法上的時(shí)候,可能會(huì)給使用者造成一些麻煩。因?yàn)槭褂萌≈灯鞯臅r(shí)候,可能下一步我們需要對(duì)取到的值進(jìn)行一些操作,而鏈?zhǔn)秸{(diào)用返回的是對(duì)象本身。
為了保持鏈?zhǔn)秸{(diào)用能使用,return this;是不能動(dòng)的,那么要對(duì)取到的值進(jìn)行操作的話,就應(yīng)該在取值器內(nèi)部進(jìn)行,將我們需要的操作過(guò)程封裝成函數(shù)傳入取值器,將值作為自定義函數(shù)的參數(shù),這就是典型的回調(diào)函數(shù)思想。
(function() { function _$(els) { ... } _$.prototype = { getValue: function(callback) { callback.call(this, this.value); // 通過(guò)傳入回調(diào)函數(shù)對(duì)取到的值進(jìn)行操作 return this; // 同時(shí)不影響繼續(xù)鏈?zhǔn)秸{(diào)用 } ... } })();
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/78066.html
摘要:異步請(qǐng)求線程在在連接后是通過(guò)瀏覽器新開(kāi)一個(gè)線程請(qǐng)求將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件循環(huán)隊(duì)列中。 基礎(chǔ):瀏覽器 -- 多進(jìn)程,每個(gè)tab頁(yè)獨(dú)立一個(gè)瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核) 每個(gè)瀏覽器渲染進(jìn)程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱(chēng)為JS內(nèi)核,負(fù)責(zé)處理Javascript腳本程序。(例如V8引擎) JS引擎線程負(fù)...
摘要:語(yǔ)言精粹讀書(shū)筆記第四章函數(shù)函數(shù)字面量函數(shù)字面量包含個(gè)部分第一部分,保留字第二部分,函數(shù)名,它可以被忽略。這個(gè)超級(jí)延遲綁定使得函數(shù)對(duì)高度復(fù)用。構(gòu)造器調(diào)用模式一個(gè)函數(shù),如果創(chuàng)建的目的就是希望結(jié)合的前綴來(lái)調(diào)用,那它就被稱(chēng)為構(gòu)造器構(gòu)造。 《JavaScript 語(yǔ)言精粹》 讀書(shū)筆記 第四章 函數(shù) Functions 函數(shù)字面量 函數(shù)字面量包含4個(gè)部分: 第一部分, 保留字 function...
摘要:寫(xiě)在前面這一章的順序?qū)τ谖唇佑|過(guò)使用過(guò)的童鞋而言略抽象了,前邊幾章主要為了說(shuō)明和之前的異步方式相比有什么優(yōu)勢(shì)和它能解決什么問(wèn)題,后邊才詳解的設(shè)計(jì)和各種場(chǎng)景下如何使用。建議先了解和簡(jiǎn)單使用過(guò)后再閱讀,效果更佳。 寫(xiě)在前面:Promise這一章的順序?qū)τ谖唇佑|過(guò)使用過(guò)Promise的童鞋而言略抽象了,前邊幾章主要為了說(shuō)明Promise和之前的異步方式相比有什么優(yōu)勢(shì)和它能解決什么問(wèn)題,后邊才...
摘要:而微服務(wù)將這個(gè)理念應(yīng)用在獨(dú)立的服務(wù)上。微服務(wù)對(duì)比與原來(lái)的單體應(yīng)用,有它的優(yōu)勢(shì),如服務(wù)的自治性增強(qiáng)但同時(shí)也會(huì)帶來(lái)一些其他問(wèn)題,如性能復(fù)雜度等問(wèn)題。想要使用微服務(wù),首先是要清楚哪些業(yè)務(wù)或者功能應(yīng)該成為單獨(dú)的服務(wù)。其次,考慮業(yè)務(wù)極有可能的變化。 1、在學(xué)習(xí)軟件構(gòu)造、設(shè)計(jì)相關(guān)知識(shí)時(shí),大家應(yīng)該有學(xué)習(xí)到內(nèi)聚性的概念:即把因相同原因而變化的東西聚合到一起,而把因不同原因而變化的東西分離開(kāi)來(lái)。而 微服...
摘要:技巧使你的更加專(zhuān)業(yè)這是上關(guān)于技巧的一篇譯文,另外你也可以在本項(xiàng)目看到原文。列舉了一些很實(shí)用的技巧,比如給空內(nèi)容的標(biāo)簽添加內(nèi)容,逗號(hào)分隔列表等等。排序算法看源碼,把它背下來(lái)吧排序算法的封裝。主要幫助初學(xué)者更好的掌握排序算法的實(shí)現(xiàn)。 成為專(zhuān)業(yè)程序員路上用到的各種優(yōu)秀資料、神器及框架 成為一名專(zhuān)業(yè)程序員的道路上,需要堅(jiān)持練習(xí)、學(xué)習(xí)與積累,技術(shù)方面既要有一定的廣度,更要有自己的深度。 Java...
閱讀 1631·2021-10-14 09:43
閱讀 5534·2021-09-07 10:21
閱讀 1279·2019-08-30 15:56
閱讀 2131·2019-08-30 15:53
閱讀 1236·2019-08-30 15:44
閱讀 2013·2019-08-30 15:44
閱讀 1323·2019-08-29 17:24
閱讀 757·2019-08-29 15:19