使用合適的設計模式一步步優化前端代碼

做者:曉飛 本文原創,轉載請註明做者及出處javascript


在後端語言中,設計模式應用的較爲普遍。如Spring中常見的工廠模式、裝飾者模式、單例模式、迭代器模式。可是在平常的前端開發中,設計模式使用的較少,或者你們的代碼已經遵循了某某設計模式可是咱們並不知道。常見的設計模式有23種,若是單純的按照模式名稱+名詞解釋的方式來寫這篇文章,可能太枯燥了或者很難理解記憶,因此我打算換一種方式。下面咱們以一個例子開始咱們今天的文章。前端

假設咱們有一個這樣的需求:
let page = {
  init: ()=>{
    //此處(placeA)有不少業務代碼或者調用了不少page中的其餘初始化函數
  },
  ....
};
複製代碼

如今業務迭代,須要咱們在page.init()初始化代碼塊的最後增長一些功能,同時不影響原先的功能。按照正常的寫法,咱們可能會像下面這樣寫:vue

let page = {
  init: ()=>{
    //placeA
    page.newFunction();
  },
  newFunction: ()=>{
    ...
  }
};
複製代碼

這樣寫是能夠解決咱們的需求,可是這樣的代碼是具備侵略性的,咱們不得不在原先的代碼的合適位置新增咱們須要的代碼。但咱們思考一個問題,若是咱們用了某個插件或者某個被ungly、minify以後的代碼呢,咱們怎麼在找到合適的位置添加咱們須要的功能呢?你們能夠先本身思考一下,再看下面的內容。java

首先咱們先看解決方案,再思考其背後的東西。
//咱們能夠在Function的原型鏈上定義一個擴展函數,以實現咱們的需求。
Function.prototype.fnAfter = function(fn) {
  var _self = this;
  return function() {
    _self.apply(this, arguments);
    fn.apply(this, arguments);
  }
};

page.init  = (page.init || function() {}).fnAfter(function() {
  console.log('咱們要追加的功能成功啦~');
});

page.init();
複製代碼

上面的代碼已經可以實現咱們的須要了,可是其實仍是不夠好或者能夠寫的更靈活一些。由於我但願能夠能夠作到像jquery的鏈式調用那樣,能夠一直日後面追加新的功能。那麼咱們在上面代碼的基礎上再擴展下,其實很簡單,咱們只要再Function.prototype.fnAfter中再返回自身就行了。jquery

Function.prototype.fnAfter = function(fn) {
  var _self = this;
  return function() {
    var fnOrigin = _self.apply(this, arguments);
    fn.apply(this, arguments);
    return fnOrigin;
  }
};
複製代碼

其實上面的代碼寫法仍是能夠優化的。好比:redux

//每次擴展的時候咱們都須要這麼寫
page.init  = (page.init || function() {}).fnAfter(function() {
  //...
});
//咱們能不能再優化下,好比容錯代碼 || function(){} 在一個地方統一處理 
//或者咱們新建一個工廠函數來幫咱們統一作這樣的事情,這裏咱們就不展開了,文章篇幅有限。
複製代碼
咱們上面的擴展其實就是遵循的是面向對象程序設計中的開放-封閉原則(OCP)。官方對OCP的解釋是:軟件實體(類、模塊、函數...)應該是能夠擴展的,可是不可修改。設計模式中有不少模式都遵循了開發-封閉原則,好比:發佈-訂閱者模式、模板方法模式、策略模式、代理模式。

有的時候咱們經過擴展來提升代碼的靈活性並不能解決全部的場景須要,在不可避免發生修改的時候,咱們能夠經過增長配置文件,讓用戶修改配置文件以實現個性化需求也是合理的。修改配置遠比修改源代碼要簡單的多。小程序

有了上面的引入,咱們來看幾個前端開發中常見的設計模式。
  • 單例模式後端

    單例模式顧名思義:保證一個類僅有一個實例,  
    而且對外暴露一個可以訪問到它的訪問點。
    複製代碼

    實現單例模式的核心就是保證一個類僅有一個實例,那麼意思就是當建立一個對象時,咱們須要判斷下以前有沒有建立過該實例,若是建立過則返回以前建立的實例,不然新建。微信小程序

    var fn = function() {
      this.instance = null;
    };
    fn.getInstance = function() {
      //寫法1
      if (!this.instance) {
        this.instance = new fn();
      }
      return this.instance;
      
      //寫法2
      return this.instance || (this.instance = new fn());
    };
    
    var fnA = fn.getInstance();
    var fnB = fn.getInstance();
    console.log(fnA === fnB); //true
    複製代碼

    平常的業務場景中,單例模式也比較常見,好比:一個頁面中的模態框只有一個,每次打開與關閉的都應該是同一個,而不是重複新建。並且爲了性能優化,咱們應該在須要時再建立,而不是頁面初始化時就已經存在於dom中,這個就是惰性單例模式設計模式

    //假設咱們須要點擊某個按鈕時就顯示出模態框,那麼咱們能夠像下面這麼實現。
    var createModal = (function(){
      var modal = null;
      return function() {
        if (!modal) {
          modal = document.createElement('div');
          //...
          modal.style.display = 'none';
          document.getElementById('container').append(modal);
        }
        return modal;
      }
    })();
    
    document.getElementById('showModal').click(function() {
      var modal = createModal();
      modal.style.display = 'block';
    });
    複製代碼

    上面的代碼中,咱們將建立對象和管理實例的邏輯都放在一個地方,違反了單一職責原則,咱們應該單獨新建一個用於建立單例的方法,這樣咱們不只能建立惟一的modal實例,也能建立其餘的,職責分開。

    var createSingleInstance = function(fn) {
      var instance = null;
      return function() {
        if (!instance) {
          instance = fn.apply(this, arguments);
        }
        return instance;
      }
    };
    
    var createModal = function() {
      var modal = docuemnt.createElement('div');
      //...
      modal.style.display = 'none';
      document.getElementById('container').append(modal);
      return modal;
    };
    
    var modal = createSingleInstance(createModal);
    複製代碼

  • 觀察者模式

    定義了對象與其餘對象之間的依賴關係,  
    當某個對象發生改變的時候,全部依賴到這個對象的地方都會被通知。
    複製代碼

    像knockout.js中的ko.compute以及vue中的computed函數其實就是這個模式的實踐。實現觀察者模式的核心就是咱們須要有一個變量來保存全部的依賴,一個listen函數用於向變量中添加依賴,一個trigger函數用於觸發通知。

    var observal = {
      eventObj: {},
      listen: function(key, fn) {
        this.eventObj[key] = this.eventObj[key] || [];
        this.eventObj[key].push(fn);
      },
      trigger: function(key) {
        var eventList = this.eventObj[key];
        if (!eventList || eventList.length < 1) {
          return;
        }
        var length = eventList.length;
        for (var i = 0; i < length; i++) {
          var event = eventList[i];
          event.apply(this, arguments);
        }
      }
    };
    
    //定義要監聽的事件
    observal.listen('command1', function() {
      console.log('黑夜給了我夜色的眼睛~');
    });
    observal.listen('command1', function() {
      console.log('我卻用它尋找光明~');
    });
    observal.listen('command2', function() {
      console.log('一花一世界~');
    });
    observal.listen('command2', function() {
      console.log('一碼一人生~');
    });
    
    //觸發某個監聽的事件
    observal.trigger('command1');//黑夜給了我夜色的眼睛~ 我卻用它尋找光明~
    observal.trigger('command2');//一花一世界~ 一碼一人生~
    複製代碼

    使用觀察者模式(發佈-訂閱模式)咱們可使得代碼更靈活、健壯性更高。訂閱者不須要了解消息來自哪個發佈者,發佈者也不須要知道消息會發送給哪些訂閱者。

    一樣的咱們能夠建立一個公用的函數庫,裏面存放建立observal的工具方法,須要用到的地方咱們就用這個方法建立一個發佈訂閱對象。

  • 其餘設計模式及設計原則

    設計模式有不少,這裏篇幅有限就再也不展開。GoF在1995年提出了23種設計模式。諸如策略者模式優化表單驗證、代理模式、組合模式、裝飾者模式、適配器模式...這些後期能夠再簡單探討或者你們後面本身瞭解。經常使用的設計模式及設計原則能夠參考下面的思惟導圖。

    經常使用設計模式

    六大設計原則

看了上面的文章,相信你們對設計模式的好處有了直觀的瞭解,也大體掌握了單例模式及觀察者模式。

設計模式都是通過了大量的代碼、軟件實踐而總結出來的優秀的組織實踐方案。每種設計模式都有它的適應場景,有的場景也會使用多種設計模式。只有瞭解了更多的設計模式,掌握各個設計模式本身的適應場景,才能更好的爲咱們所用。

可是***過早的優化不必定是好事或者不是必須的***,有時候咱們能夠一開始並不去優化,等到某個應用場景下出現了代碼組織混亂、須要額外擴展等問題,咱們再優化重構,以防過早優化致使的沒必要要性或者只是增長了代碼沒必要要的複雜性。就像redux,若是一個頁面組件與組件之間有數據共享、須要在任意組件內部拿到某個數據、任意一個組件中某個行爲致使的數據變化須要通知到全部用到的地方,那麼這個時候可使用redux,一些簡單的表單頁面或者展現頁徹底能夠不用redux。

看到這裏不容易,最後給你們講一個笑話輕鬆一下:
從前有隻麋鹿,它在森林裏玩兒,不當心走丟了。  
因而它給它的好朋友長頸鹿打電話:「喂…我迷路辣。」  
長頸鹿聽見了回答說:「喂~我長頸鹿辣~」
複製代碼

參考:曾探《javascript設計模式與開發實踐》


iKcamp官網:www.ikcamp.com

訪問官網更快閱讀所有免費分享課程:《iKcamp出品|全網最新|微信小程序|基於最新版1.0開發者工具之初中級培訓教程分享》。 包含:文章、視頻、源代碼

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

iKcamp最新活動

報名地址:www.huodongxing.com/event/54099…

「每天練口語」小程序總榜排名第4、教育類排名第一的研發團隊,面對面溝通交流。


2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!

相關文章
相關標籤/搜索