HTML前端各種常見的設計模式
前言
HTML設計模式定義:在面向對象軟件設計過程中 針對特定問題的簡潔而優雅的解決方案。在不同的編程語言中,對設計模式的實現其實是可能會有區別的。
- 單例模式
- 觀察者模式
- 工廠模式
- 命令模式
- 職責鏈模式
1,單例模式
定義:是保證一個類只有一個實例,並且提供一個訪問它的全局訪問點。
需求:一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽器中的window對象、登錄浮窗等。
實現:用一個變量標識當前是否已經為某個類創建過對象,如果是,則在下一次獲取這個類的實例時,直接返回之前創建的對象。
優點:
- 可以用來劃分命名空間,減少全局變量的數量
- 可以被實例化,且實例化一次,再次實例化生成的也是第一個實例
基礎栗子:
// 單例模式 var Singleton = function(name){ this.name = name; this.instance = null; }; Singleton.prototype.getName = function(){ return this.name; }; // 獲取實例對象 Singleton.getInstance = function(name) { if(!this.instance) { this.instance = newSingleton(name); } return this.instance; }; // 測試單例模式的實例 var a = Singleton.getInstance("aa"); var b = Singleton.getInstance("bb"); console.log(a===b) // true
實踐栗子:
(function () { //管理單例的邏輯代碼,如果沒有數據則創建,有數據則返回 var getSingle = function(fn){ //參數為創建對象的方法 var result; returnfunction(){ //判斷是Null或賦值 return result || (result = fn.apply(this,arguments)); }; }; //創建登錄窗口方法 var createLoginLayer = function(){ var div = document.createElement(‘div‘); div.innerHTML = ‘我是登錄浮窗‘; div.style.display = ‘none‘; document.body.appendChild(div); return div; }; //單例方法 var createSingleLoginLayer = getSingle(createLoginLayer); //使用惰性單例,進行創建 document.getElementById(‘loginBtn‘).onclick = function(){ var loginLayer = createSingleLoginLayer(); loginLayer.style.display = ‘block‘; }; })()
2,觀察者模式
定義:對象間的一種一對多的依賴關系。
需求:當一個對象的狀態發生變化時,所有依賴於他的對象都將得到通知。
優點:時間上的解耦,對象之間的解耦。
實現:
- 首先,指定好誰充當發布者;
- 然後,給發布者添加一個緩存列表,用於存放回調函數以便通知訂閱者;
- 最後,發布消息的時候,發布者會遍歷這個緩存列表,依次觸發裏面存放的訂閱者回調函數。
基礎栗子:
var salesOffices = {}; // 定義售樓處 salesOffices.clientList = []; // 緩存列表,存放訂閱者的回調函數 salesOffices.listen = function( fn ){ // 增加訂閱者 this.clientList.push( fn ); // 訂閱的消息添加進緩存列表 }; salesOffices.trigger = function(){ // 發布消息 for( var i = 0, fn; fn = this.clientList[ i++ ]; ){ fn.apply( this, arguments ); // arguments 是發布消息時帶上的參數 } }; //調用 salesOffices.listen( function( price, squareMeter ){//訂閱消息 console.log( ‘價格= ‘ + price ); console.log( ‘squareMeter= ‘ + squareMeter ); }); salesOffices.trigger( 2000000, 88 ); // 輸出:200 萬,88 平方米
實踐栗子:登錄頁面登錄後,會需要刷新各個模塊的信息(頭像、nav)這類。
var ObserverEvent = (function () { var clientList = [], listen, trigger, remove; listen = function (key, fn) { if (!clientList[key]) { clientList[key] = []; } clientList[key].push(fn); }; trigger = function () { var key = Array.prototype.shift.call(arguments), fns = clientList[key]; if (!fns || fns.length === 0) { return false; } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } }; remove = function (key, fn) { var fns = clientList[key]; if (!fns) { return false; } if (!fn) { fns && (fns.length = 0); } else { for (var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l]; if (_fn === fn) { fns.splice(l, 1); } } } }; return { listen:listen, trigger:trigger, remove:remove } })(); ObserverEvent.listen(‘squareMeter88‘, fn1 = function (price) { console.log(‘價格=‘ + price); }); ObserverEvent.listen(‘squareMeter100‘, function (price) { console.log(‘價格=‘ + price); }); ObserverEvent.trigger(‘squareMeter88‘, 200000); //刷新模塊信息 var header = (function () { ObserverEvent.listen(‘loginSucc‘, function (data) { header.setAvatar(data.avatar); }); return { setAvatar: function (data) { console.log(data + "設置header成功"); } } })(); var nav = (function () { ObserverEvent.listen(‘loginSucc‘, function (data) { nav.setAvatar(data.avatar) }); return { setAvatar: function (data) { console.log(data + ‘設置nav成功‘); } } })(); var data = {}; data.avatar = "參數"; ObserverEvent.trigger(‘loginSucc‘, data);
3,工廠模式:
定義:將其成員對象的實例化推遲到子類來實現的類。
需求:創建對象的流程賦值的時候,比如依賴於很多設置文件等 ;處理大量具有相同屬性的小對象;註:不能濫用
優點:不暴露創建對象的具體邏輯,而是將將邏輯封裝在一個函數中。
分類:簡單工廠,工廠方法和抽象工廠。
實現:
3.1 簡單工廠模式 (創建單一對象,需要的類比較少)
let UserFactory = function (role) { function SuperAdmin() { this.name = "超級管理員", this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘, ‘應用數據‘, ‘權限管理‘] } function Admin() { this.name = "管理員", this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘, ‘應用數據‘] } function NormalUser() { this.name = ‘普通用戶‘, this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘] } switch (role) { case ‘superAdmin‘: return new SuperAdmin(); break; case ‘admin‘: return new Admin(); break; case ‘user‘: return new NormalUser(); break; default: throw new Error(‘參數錯誤, 可選參數:superAdmin、admin、user‘); } }
3.2 工廠方法模式 (創建多類對象,需要的類比較多)
為方便後續新增類方便,只需改一處代碼,封裝了工廠方法而已。並且把類都放在工廠類原型中實現。
//安全模式創建的工廠方法函數 let UserFactory = function(role) { if(this instanceof UserFactory) { var s = new this[role](); return s; } else { return new UserFactory(role); } } //工廠方法函數的原型中設置所有對象的構造函數 UserFactory.prototype = { SuperAdmin: function() { this.name = "超級管理員", this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘, ‘應用數據‘, ‘權限管理‘] }, Admin: function() { this.name = "管理員", this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘, ‘應用數據‘] }, NormalUser: function() { this.name = ‘普通用戶‘, this.viewPage = [‘首頁‘, ‘通訊錄‘, ‘發現頁‘] } } //調用 let superAdmin = UserFactory(‘SuperAdmin‘); let admin = UserFactory(‘Admin‘) let normalUser = UserFactory(‘NormalUser‘)
3.3 抽象工廠模式 (創建父類,子類繼承父類,具體實現在子類)
抽象工廠其實是實現子類繼承父類的方法,只是一個方法。
抽象工廠模式一般用在多人協作的超大型項目中,並且嚴格的要求項目以面向對象的思想進行完成。
// 抽象工廠方法 var VehicleFatory = function(subType, superType) { // 判斷抽象工廠中是否有該抽象類 if(typeof VehicleFactory[superType] === ‘function‘) { // 緩存類 function F() {}; // 繼承父類屬性和方法 F.prototype = new VehicleFactory[superType] (); // 將子類constructor 指向子類 subType.constructor = subType; // 子類原型繼承‘父類‘ subType.prototype = new F(); } else { // 不存在該抽象類拋出錯誤 throw new Error(‘未創建該抽象類‘); } }; // 小汽車抽象類 VehicleFactory.Car = function() { this.type = ‘car‘; }; VehicleFactory.Car.prototype = { getPrice: function() { return new Error(‘抽象方法不能調用‘); }, getSpeed: function() { return new Error(‘抽象方法不能調用‘); } }; // 公交車抽象類 VehicleFactory.Bus = function() { this.type = ‘bus‘; }; VehicleFactory.Bus.prototype = { getPrice: function() { return new Error(‘抽象方法不能調用‘); }, getSpeed: function() { return new Error(‘抽象方法不能調用‘); } }; // 貨車抽象類 VehicleFactory.Truck = function() { this.type = ‘truck‘; }; VehicleFactory.Truck.prototype = { getPrice: function() { return new Error(‘抽象方法不能調用‘); }, getSpeed: function() { return new Error(‘抽象方法不能調用‘); } }; // 創建產品子類繼承相應的產品簇抽象類 // 寶馬汽車子類 var BMW = function(price, speed) { this.price = price; this.speed = speed; } //抽象工廠實現對Car抽象類的繼承 VehicleFactory(BMW, ‘Car‘); BMW.prototype.getPrice = function() { return this.price }; BMW.prototype.getSpeed = function() { return this.speed }; // 公交車... // 貨車...
4,命令模式:
定義:用來對方法調用進行參數化處理和傳送,經過這樣處理過的方法調用可以在任何需要的時候執行。
需求:有時候需要向某些對象發送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是什麽,此時希望用一種松耦合的方式來設計軟件,使得請求發送者和請求接收者能夠消除彼此之間的耦合關系。
實現:將函數的調用、請求和操作封裝成一個單一的對象。
1 var setCommand = function(button,func) { 2 button.onclick = function(){ 3 func(); 4 } 5 }; 6 var MenuBar = { 7 refersh: function(){ 8 alert("刷新菜單界面"); 9 } 10 }; 11 var SubMenu = { 12 add: function(){ 13 alert("增加菜單"); 14 } 15 }; 16 // 刷新菜單 17 var RefreshMenuBarCommand = function(receiver) { 18 return function(){ 19 receiver.refersh(); 20 }; 21 }; 22 // 增加菜單 23 var AddSubMenuCommand = function(receiver) { 24 return function(){ 25 receiver.add(); 26 }; 27 }; 28 var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar); 29 // 增加菜單 30 var addSubMenuCommand = AddSubMenuCommand(SubMenu); 31 setCommand(b1,refershMenuBarCommand); 32 33 setCommand(b2,addSubMenuCommand);
5,職責鏈模式:
定義:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系,將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它為止。(大函數分割成一個個小函數,清晰,各司其職)
需求:代碼不清晰,可讀性差,拆分函數。
實現:
//----------------------改造前---------------
var order = function( orderType, pay, stock ){ if ( orderType === 1 ){ // 500 元定金購買模式 if ( pay === true ){ // 已支付定金 console.log( ‘500 元定金預購, 得到 100 優惠券‘ ); }else{ // 未支付定金,降級到普通購買模式 if ( stock > 0 ){ // 用於普通購買的手機還有庫存 console.log( ‘普通購買, 無優惠券‘ ); }else{ console.log( ‘手機庫存不足‘ ); } } } else if ( orderType === 2 ){ // 200 元定金購買模式 if ( pay === true ){ console.log( ‘200 元定金預購, 得到 50 優惠券‘ ); }else{ if ( stock > 0 ){ console.log( ‘普通購買, 無優惠券‘ ); }else{ console.log( ‘手機庫存不足‘ ); } } } else if ( orderType === 3 ){ if ( stock > 0 ){ console.log( ‘普通購買, 無優惠券‘ ); }else{ console.log( ‘手機庫存不足‘ ); } } }; order( 1 , true, 500); // 輸出: 500 元定金預購, 得到 100 優惠券
//--------------------- 改造後----------------------------
// 500 元訂單 var order500 = function( orderType, pay, stock ){ if ( orderType === 1 && pay === true ){ console.log( ‘500 元定金預購, 得到 100 優惠券‘ ); }else{ order200( orderType, pay, stock ); // 將請求傳遞給 200 元訂單 } }; // 200 元訂單 var order200 = function( orderType, pay, stock ){ if ( orderType === 2 && pay === true ){ console.log( ‘200 元定金預購, 得到 50 優惠券‘ ); }else{ orderNormal( orderType, pay, stock ); // 將請求傳遞給普通訂單 } }; // 普通購買訂單 var orderNormal = function( orderType, pay, stock ){ if ( stock > 0 ){ console.log( ‘普通購買, 無優惠券‘ ); }else{ console.log( ‘手機庫存不足‘ ); } }; // 測試結果: order500( 1 , true, 500); // 輸出:500 元定金預購, 得到 100 優惠券 order500( 1, false, 500 ); // 輸出:普通購買, 無優惠券 order500( 2, true, 500 ); // 輸出:200 元定金預購, 得到 500 優惠券 order500( 3, false, 500 ); // 輸出:普通購買, 無優惠券 order500( 3, false, 0 ); // 輸出:手機庫存不足
HTML前端各種常見的設計模式