1. 程式人生 > >javascript常用設計模式介紹,實現及實際應用(一)

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){
  //登入失敗操作
}

本文就介紹到這,其它幾種模式將在第二篇文章進行介紹。