前言
潛水了一段時間,今天空閒時間覆盤下之前的知識點,聊聊 js 幾種常見的設計模式。
掌握 JavaScript 中常見的一些設計模式,對我們書寫規範性程式碼,可維護性程式碼有很大的幫助。
ps:最近在一些好友的鼓勵下,pubdreamcc 準備著手經營一個公眾號了,具體資訊會在接下來的兩天時間內釋出,新手上路,歡迎大夥提供一些寶貴的建議,cc 在這裡先謝了~
內容主體
單例模式
所謂單例模式即為:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
這裡其實利用的是 js閉包
來實現這樣的功能。
假如現在我們有這樣的需求,設定一個管理員,無論建立多次都只是設定一次。
function SetManager(name) {
this.manager = name;
}
SetManager.prototype.getName = function() {
console.log(this.manager);
};
var SingletonSetManager = (function() {
var manager = null;
return function(name) {
if (!manager) {
manager = new SetManager(name);
}
return manager;
}
})();
SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a
這種方法有一個缺點就是:如果我們需要再次建立一個 HR,則需要將程式碼再複製一遍,所以我們可以提取通用的單例。
function getSingleton(fn) {
var instance = null;
return function() {
if (!instance) {
instance = fn.apply(this, arguments);
}
return instance;
}
}
// 設定管理員
var managerSingleton = getSingleton(function(name){
var manager = new SetManager(name);
return manager;
})
managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a
// 設定 HR
function SetHr(name) {
this.hr = name;
}
SetHr.prototype.getName = function() {
console.log(this.hr);
};
var hrSingleton = getSingleton(function(name) {
var hr = new SetHr(name);
return hr;
});
hrSingleton('aa').getName(); // aa
hrSingleton('bb').getName(); // aa
hrSingleton('cc').getName(); // aa
這樣我們的程式碼可通用性就會變得更好,省去了一些重複性的程式碼。
代理模式
所謂代理模式就是:我們不方便直接訪問某個物件時,可以為物件建立一個佔位符(代理),以便控制對它的訪問,我們實際上訪問的是代理物件。
這裡我們以一個過濾敏感字元來說明這種模式
// 主體,傳送訊息
function sendMsg(msg) {
console.log(msg);
}
// 代理,對訊息進行過濾
function proxySendMsg(msg) {
// 無訊息則直接返回
if (typeof msg === 'undefined') {
console.log(null);
return;
}
// 有訊息則進行過濾
msg = ('' + msg).replace(/泥\s*煤/g, '');
sendMsg(msg);
}
sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀
proxySendMsg('泥煤呀泥 煤'); // 呀
proxySendMsg(); // null
這樣操作的意圖很明顯,當沒有訊息的時候,控制對主體物件的訪問,代理直接返回一個 null
,有訊息,則會過濾掉敏感字元,實現虛擬代理。
策略模式
策略模式就是內部封裝一些演算法,它們之間可以互相替換,但是它們不隨客戶端變化而變化。
策略模式我們外部看不到演算法的具體實現,我們也只關心演算法實現的結果,不關注過程。
這裡以一個商品促銷的例子來說明下:在聖誕節,某些商品需要八折出售,有些商品需要九折出售,到了元旦節,普通客戶滿100減30,vip客戶滿100減50。可以看到商品出售的價格需要根據不同的條件來規定,分別採取不同的演算法實現,所以我們採用策略模式。
// 價格策略物件
class PriceStrategy {
constructor() {
// 內部演算法物件
this.stragtegy = {
// 100返30
return30(price) {
return +price + parseInt( price / 100) * 30;
},
// 100 返 50
return50(price) {
return +price + parseInt(price/ 100) * 50;
},
// 9 折
percent90(price) {
return price * 100 * 90 / 10000
},
percent80(price) {
return price * 100 * 80 / 10000
},
percent50(price) {
return price * 100 * 50 / 10000
}
}
}
// 策略演算法呼叫介面
getPrice(algorithm, price) {
return this.stragtegy[algorithm] && this.stragtegy[algorithm](price);
}
}
let priceStrategy = new PriceStrategy();
let price = priceStrategy.getPrice('return50', 314.67);
console.log(price);
這樣,我們可以採取不同的策略演算法得到商品的不同價格。
觀察者模式
觀察者模式又稱為 “釋出-訂閱模式”,通過定義一種依賴關係,當一個物件狀態發生改變時,訂閱者會得到通知。
其實,我們傳統的 DOM 事件繫結就是一種釋出-訂閱模式。
// 訂閱
document.body.addEventListener('click', function() {
console.log('click1');
}, false);
document.body.addEventListener('click', function() {
console.log('click2');
}, false);
// 釋出
document.body.click(); // click1 click2
裝飾者模式
裝飾者模式就是在不改變原物件基本功能的基礎上,通過增加功能使得原本物件滿足使用者的更為複雜的需求。
比如有這麼一個需求:
使用者點選輸入框時,如果輸入框輸入的內容有限制,那麼在其後面顯示使用者輸入內容的限制格式的提示文案
---------------->>>>>>> 現在要改為:
多加一條需求,預設輸入框上邊顯示一行文案,當使用者點選輸入框的時候,文案消失。
這裡是以前的程式碼:
// 輸入框元素
let telInput = document.getElementById('tel_input');
// 輸入框提示文案
let telWarnText = document.getElementById('tel_warn_text');
// 點選輸入框顯示輸入框輸入格式提示文案
input.onclick = function () {
telWarnText.style.display = 'inline-block';
};
修改之後的程式碼:
// 輸入框元素
let telInput = document.getElementById('tel_input');
// 輸入框輸入格式提示文案
let telWarnText = document.getElementById('tel_warn_text');
// 輸入框提示輸入文案
let telDemoText = document.getElementById('tel_demo_text');
// 點選輸入框顯示輸入框輸入格式提示文案
input.onclick = function () {
telWarnText.style.display = 'inline-block';
telDemoText.style.display = 'none';
};
但是緊接著悲劇就來了,修改了電話輸入框,還有姓名、地址輸入框等等;
裝飾已有的功能物件
原有的功能已經不滿足使用者的需求了,此時需要做的是對原有的功能新增,設定新的屬性和方法來滿足新的需求,但是有不影響原來已經有的部分。
let decorator = function (input, fn) {
let getInput = document.getElementById(input);
if(typeof getInput.onclick === 'function') {
let oldClick = getInput.onclick;
getInput.onclick = function() {
// 原來的事件回撥函式
oldClick();
// 新增的事件回撥函式
fn();
}
} else {
getInput.onclick = fn;
}
// 其他事件
};
// 電話輸入框功能裝飾
decorator('tel_input', function() {
document.getElementById('tel_demo_text').sytle.display = 'none'
});
// 姓名輸入框裝飾
decorator('name_input', function() {
document.getElementById('name_demo_text').sytle.display = 'none'
});
// 地址輸入框裝飾
decorator('address_input', function() {
document.getElementById('address_demo_text').sytle.display = 'none'
});
後語
本編文章出自於我的 github
倉庫 ,歡迎喜歡的夥伴 star
,謝謝 。
倉庫地址 前端學習