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