javascript常用設計模式介紹,實現及實際應用(一)
javascript設計模式介紹,實現及實際應用(一)
本文將介紹javascript中常用的設計模式原理和實現,並結合例項講解其應用。
本篇文章先介紹單例模式,策略模式,代理模式,釋出訂閱模式和命令模式,其它幾種模式後續文章將繼續介紹。
1、單例模式
單例模式就是一個例項在整個網頁的生命週期裡只建立一次,後續再呼叫例項建立函式的時候,返回的仍是之前建立的例項。在實際開發中應用十分廣泛,例如頁面中的登入框,顯示訊息的提示窗,都只需要一個例項即可。
我們以訊息提示框為例,展示如何實現單例模式。程式碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>singleTon</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* 提示框動畫和樣式 */
@keyframes appear{
from {
left: 100%;
}
to {
left: 50%;
}
}
.message-tip{
animation: appear .5s linear;
position: fixed;
width: 200px;
background: red;
color: #fff;
line-height: 1.5;
padding-left: 5px;
top: 20px;
left: 50%;
border: 1px solid #d3d3d3;
transform : translateX(-50%);
}
</style>
</head>
<body>
<button id="tipsBtn" onclick="showMsg()">提示訊息</button>
<script>
//建立提示框單例
let createTips = (function(){
let tipDom = null;
function createDom() {
let div = document.createElement('div');
div.setAttribute('class', 'message-tip');
return div;
}
return function(msg) {
if (!tipDom) {
tipDom = createDom();
}
tipDom.innerHTML = msg;
return tipDom;
}
})();
//驗證是夠是單例,列印true,則是單例模式
console.log(createTips('1') === createTips('2'));
function showMsg() {
let tips = createTips('這是訊息提示');
document.body.appendChild(tips);
setTimeout(() => {
document.body.removeChild(tips);
}, 5000);
}
</script>
</body>
</html>
程式碼講解:使用createTips函式建立的div時,如果原先已經建立了div,則直接呼叫原來的div,不再重新建立。除了提示框,網頁中一些登入框等等也可以用單例模式實現,網頁的整個生命週期裡,只有一個dom被建立。或者js的程式碼中的某個例項如果共享的,只需要一次,就使用單例模式建立。window物件也是一個單例,整個生命週期裡只有一個window物件。
2、策略模式
策略模式是指將策略(演算法)封裝起來,策略的目的是將演算法和使用分離開。
我們假設有這樣一個應用:
年終績效評定,有S,A,B,C四種績效,對應年終獎為4,3,2,1個月工資,設計函式,計算績效。函式如下:
function calBonus(grade, salary) {
if (grade === 'S') {
return salary*4;
} else if (grade === 'A') {
return salary*3;
} else if (grade === 'B') {
return salary*2;
} else {
return salary;
}
}
函式裡各種判斷條件,如果演算法實現比較複雜,這個函式會異常龐大。我們用策略模式改造函式可如下:
//計算績效為S的年終
function S(salary) {
return salary * 4;
}
//策略使用
function calBonus(fn, salary) {
return fn(salary);
}
//計算績效為S,工資為20000的年終
calBonus(S, 20000);
這種方法改寫程式碼後,程式碼的擴充套件性就好了一些,實際的演算法由函式封裝起來,程式碼的擴充套件性強了,也方便複用。
這就是策略模式的應用之一,在程式碼中,將具體的演算法或策略封裝起來,和使用的場景分離,可以提高程式碼擴充套件性和複用率。
代理模式
代理模式很好理解,我們不能直接使用目標函式,而是通過呼叫代理函式來實現對目標函式的使用。
對於網路代理這個詞語,每個同學應該都瞭解,就是無法直接上網,把上網的請求都發送到代理伺服器,由代理伺服器請求資料,然後轉發給相應人員。
現在有一個場景,某個網頁有很多請求,有部分請求是不被允許的,我們使用代理模式實現這一功能。程式碼如下:
//允許的請求地址
let urlArr = ['/aaa', '/bbb'];
//正常的ajax請求函式,url請求地址,method請求方法, data請求資料,callback回撥函式
function commonAjax(url, method, data, callback) {
//具體實現略過,這個大家應該都知道
}
//代理請求函式
function proxyAjax(url, method, data, callback) {
if (urlArr.indexOf(url) > -1) {
commonAjax(url, method, data, callback);
} else {
let data = {
status: false,
message: '該請求不被允許',
code: 403
}
callback(data);
}
}
程式碼解析:urlArr是我們允許的請求地址,其它請求不允許傳送,commonAjax是正常的請求函式,proxyAjax是代理請求函式。
注意:代理介面和實際函式介面要保持一致。
後續需求發生變化,不需要限制任何請求,直接呼叫請求函式即可。現在大部分公司聯網都是有限制的,過濾一部分網站,只有公司白名單裡的網站才能訪問,其功能和上述程式碼邏輯是一致的。
釋出訂閱模式
釋出訂閱模式在實際應用中非常常見,例如,我們在微信App上關注了某個公眾號,當該公眾號有新文章釋出時,就會通知我們。
釋出訂閱模式定義了一種一對多的依賴關係,當“一”發生變化,通知多個依賴。
為了好理解,先說明幾個名詞含義,我們以微信關注公眾號為例講解,如下:
subscriber: 訂閱者,我們關注了公眾號,我們就是訂閱者。
subject:主題,微信公眾號的唯一。
publisher: 訊息釋出者,公眾號的維護者,不斷髮布新的文章。
當然還有一個排程中心,這樣就是完整的釋出訂閱模式了。
現在我們就實現釋出訂閱模式。程式碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>釋出訂閱模式例項</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.main{
display: flex;
}
.msg-item{
width: 300px;
border-right: 3px solid #d3d3d3;
min-height: 400px;
}
</style>
</head>
<body>
<div class="main">
<div class="msg-item">
<div>
<input type="text" id="subject" placeholder="輸入訂閱主題" style="width: 150px">
<button onclick="subscribe()">訂閱訊息</button>
</div>
<h3>已訂閱訊息列表</h3>
<ul id="subscribe_list">
</ul>
<h3>接收到訊息列表</h3>
<ul id="message_list">
</ul>
</div>
<div class="msg-item">
<input type="text" id="pub_subject" placeholder="輸入釋出主題" style="width: 150px">
<input type="text" id="pub_message" placeholder="輸入釋出訊息" style="width: 200px">
<button onclick="publishMsg()">釋出訊息</button>
<h3>釋出訊息列表</h3>
<ul id="publish_list">
</ul>
</div>
</div>
<script>
//排程中心演算法實現
function pubsub() {
//主題列表
this.subjects = {};
};
//釋出訊息
pubsub.prototype.publish = function(type, message) {
if (this.subjects[type]) {
this.subjects[type].list.map(item => {
//非同步呼叫
setTimeout(function(){
item.fn && item.fn(type, message);
}, 0);
});
}
}
//訂閱訊息
pubsub.prototype.subscribe = function(type, callback) {
if (!this.subjects[type]) {
this.subjects[type] = {
count: 0,
list: []
}
}
let subId = type + '-' + this.subjects[type].count++;
this.subjects[type].list.push({
fn: callback,
id: subId
})
return subId;
}
//移除訂閱,引數為訂閱時返回的id
pubsub.prototype.remove = function(id) {
let arr = id.split('-');
if (arr.length > 1) {
if (this.subjects[arr[0]]) {
let len = this.subjects[arr[0]].list.length;
for (let i = 0; i < len; i++) {
if (this.subjects[arr[0]].list[i].id === id) {
this.subjects[arr[0]].list.splice(i, 1);
return true;
}
}
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//以上是排程中心的程式碼
/**
* 訂閱函式
* 三個引數,pubsub排程中心例項,type訂閱主題,callback訂閱回撥函式
* */
function subscriber(pubsub, type, callback) {
if (pubsub && pubsub.subscribe) {
return pubsub.subscribe(type, callback);
}
return false;
}
/**
* 釋出訊息函式
* 三個引數,pubsub排程中心例項,type釋出主題,message釋出訊息
* */
function publish(pubsub, type, message) {
if (pubsub && pubsub.publish) {
pubsub.publish(type, message);
}
}
</script>
<script>
//排程中心例項
const pubsubInstance = new pubsub();
//資料全域性類,儲存頁面要展現的變數等資料
const global = {
subscribeList: [], //訂閱訊息列表
publishList: []
};
//訂閱按鈕處理函式
function subscribe() {
let type = document.getElementById('subject').value;
if (global.subscribeList.indexOf(type) === -1) {
if (subscriber(pubsubInstance, type, handleMsg)) {
global.subscribeList.push(type);
let subscribeList = document.getElementById('subscribe_list');
subscribeList.innerHTML += `<li>${type}</li>`;
}
}
}
//釋出按鈕處理函式
function publishMsg() {
let type = document.getElementById('pub_subject').value;
let message = document.getElementById('pub_message').value;
publish(pubsubInstance, type, message);
let publishList = document.getElementById('publish_list');
publishList.innerHTML += `<li>${type}: ${message}</li>`;
}
//訂閱訊息的回撥函式
function handleMsg(type, msg) {
let messageList = document.getElementById('message_list');
messageList.innerHTML += `<li>${type}: ${msg}</li>`;
}
</script>
</body>
</html>
以上是釋出訂閱模式的實現和測試程式碼,程式碼中有註釋,不難理解,就不再解釋了,測試結果如下:
左側上方是訂閱的訊息主題列表,訂閱news和food這兩個主題。
左側下方是接收到的訊息列表,接收到兩條訊息,主題分別是news和food。
右側是釋出的訊息列表,釋出了3個主題的訊息,每個主題一條(分別是news,vedio,food)。
訂閱者只訂閱了news和food新聞,所以右側釋出了3條新聞,但是關於news和food只有兩條,訂閱者只接收了兩條關注的新聞。
命令模式
所謂命令模式就是將下要執行的業務邏輯封裝到一個函式或類中,不需要具體誰來執行該命令的。在javascript中,命令模式最好的體現就是回撥函式。
//ajax請求函式
function httpRequest(url, params, method, success, error) {
//具體實現省略
$.ajax({
url: url,
data: params,
method: method,
success: function(res) {
success(res);
},
error: funtion(err) {
error(err);
}
})
}
//具體呼叫,假設有登入請求,url為"/login",post方法
httpRequest('/login', {}, 'POST', loginSucess, loginFail);
//實現請求和具體操作的解耦,所有請求均可使用httpRequest方法,具體的success和error可單獨定義。
//登入成功
function loginSuccess(data) {
//登入成功需要做的業務處理。
}
//登入失敗
function loginFail(err){
//登入失敗操作
}
本文就介紹到這,其它幾種模式將在第二篇文章進行介紹。