使用合適的設(shè)計模式一步步優(yōu)化前端代碼

作者:曉飛
本文原創(chuàng),轉(zhuǎn)載請注明作者及出處


在后端語言中,設(shè)計模式應(yīng)用的較為廣泛。如Spring中常見的工廠模式、裝飾者模式、單例模式、迭代器模式。但是在日常的前端開發(fā)中,設(shè)計模式使用的較少,或者大家的代碼已經(jīng)遵循了某某設(shè)計模式但是我們并不知道。常見的設(shè)計模式有23種,如果單純的按照模式名稱+名詞解釋的方式來寫這篇文章,可能太枯燥了或者很難理解記憶,所以我打算換一種方式。下面我們以一個例子開始我們今天的文章。

假設(shè)我們有一個這樣的需求:
let page = {
  init: ()=>{
    //此處(placeA)有很多業(yè)務(wù)代碼或者調(diào)用了很多page中的其他初始化函數(shù)
  },
  ....
};

現(xiàn)在業(yè)務(wù)迭代,需要我們在page.init()初始化代碼塊的最后增加一些功能,同時不影響原先的功能。按照正常的寫法,我們可能會像下面這樣寫:

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

這樣寫是可以解決我們的需求,但是這樣的代碼是具有侵略性的,我們不得不在原先的代碼的合適位置新增我們需要的代碼。但我們思考一個問題,如果我們用了某個插件或者某個被ungly、minify之后的代碼呢,我們怎么在找到合適的位置添加我們需要的功能呢?大家可以先自己思考一下,再看下面的內(nèi)容。

首先我們先看解決方案,再思考其背后的東西。
//我們可以在Function的原型鏈上定義一個擴展函數(shù),以實現(xiàn)我們的需求。
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();

上面的代碼已經(jīng)能夠?qū)崿F(xiàn)我們的需要了,但是其實還是不夠好或者可以寫的更靈活一些。因為我希望可以可以做到像jquery的鏈式調(diào)用那樣,可以一直往后面追加新的功能。那么我們在上面代碼的基礎(chǔ)上再擴展下,其實很簡單,我們只要再Function.prototype.fnAfter中再返回自身就好了。

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

其實上面的代碼寫法還是可以優(yōu)化的。比如:

//每次擴展的時候我們都需要這么寫
page.init  = (page.init || function() {}).fnAfter(function() {
  //...
});
//我們能不能再優(yōu)化下,比如容錯代碼 || function(){} 在一個地方統(tǒng)一處理  
//或者我們新建一個工廠函數(shù)來幫我們統(tǒng)一做這樣的事情,這里我們就不展開了,文章篇幅有限。
我們上面的擴展其實就是遵循的是面向?qū)ο蟪绦蛟O(shè)計中的開放-封閉原則(OCP)。官方對OCP的解釋是:軟件實體(類、模塊、函數(shù)...)應(yīng)該是可以擴展的,但是不可修改。設(shè)計模式中有很多模式都遵循了開發(fā)-封閉原則,比如:發(fā)布-訂閱者模式、模板方法模式、策略模式、代理模式。

有的時候我們通過擴展來提高代碼的靈活性并不能解決所有的場景需要,在不可避免發(fā)生修改的時候,我們可以通過增加配置文件,讓用戶修改配置文件以實現(xiàn)個性化需求也是合理的。修改配置遠比修改源代碼要簡單的多。

有了上面的引入,我們來看幾個前端開發(fā)中常見的設(shè)計模式。
  • 單例模式

    單例模式顧名思義:保證一個類僅有一個實例,  
    并且對外暴露一個能夠訪問到它的訪問點。
    

    實現(xiàn)單例模式的核心就是保證一個類僅有一個實例,那么意思就是當創(chuàng)建一個對象時,我們需要判斷下之前有沒有創(chuàng)建過該實例,如果創(chuàng)建過則返回之前創(chuàng)建的實例,否則新建。

    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
    

    日常的業(yè)務(wù)場景中,單例模式也比較常見,比如:一個頁面中的模態(tài)框只有一個,每次打開與關(guān)閉的都應(yīng)該是同一個,而不是重復新建。而且為了性能優(yōu)化,我們應(yīng)該在需要時再創(chuàng)建,而不是頁面初始化時就已經(jīng)存在于dom中,這個就是惰性單例模式。

    //假設(shè)我們需要點擊某個按鈕時就顯示出模態(tài)框,那么我們可以像下面這么實現(xiàn)。
    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';
    });
    

    上面的代碼中,我們將創(chuàng)建對象和管理實例的邏輯都放在一個地方,違反了單一職責原則,我們應(yīng)該單獨新建一個用于創(chuàng)建單例的方法,這樣我們不僅能創(chuàng)建唯一的modal實例,也能創(chuàng)建其他的,職責分開。

    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);
    

    ?

  • 觀察者模式

    定義了對象與其他對象之間的依賴關(guān)系,  
    當某個對象發(fā)生改變的時候,所有依賴到這個對象的地方都會被通知。
    

    像knockout.js中的ko.compute以及vue中的computed函數(shù)其實就是這個模式的實踐。實現(xiàn)觀察者模式的核心就是我們需要有一個變量來保存所有的依賴,一個listen函數(shù)用于向變量中添加依賴,一個trigger函數(shù)用于觸發(fā)通知。

    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);
        }
      }
    };
    
    //定義要監(jiān)聽的事件
    observal.listen('command1', function() {
      console.log('黑夜給了我夜色的眼睛~');
    });
    observal.listen('command1', function() {
      console.log('我卻用它尋找光明~');
    });
    observal.listen('command2', function() {
      console.log('一花一世界~');
    });
    observal.listen('command2', function() {
      console.log('一碼一人生~');
    });
    
    //觸發(fā)某個監(jiān)聽的事件
    observal.trigger('command1');//黑夜給了我夜色的眼睛~ 我卻用它尋找光明~
    observal.trigger('command2');//一花一世界~ 一碼一人生~
    

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

    同樣的我們可以創(chuàng)建一個公用的函數(shù)庫,里面存放創(chuàng)建observal的工具方法,需要用到的地方我們就用這個方法創(chuàng)建一個發(fā)布訂閱對象。

  • 其他設(shè)計模式及設(shè)計原則

    設(shè)計模式有很多,這里篇幅有限就不再展開。GoF在1995年提出了23種設(shè)計模式。諸如策略者模式優(yōu)化表單驗證、代理模式、組合模式、裝飾者模式、適配器模式...這些后期可以再簡單探討或者大家后面自己了解。常用的設(shè)計模式及設(shè)計原則可以參考下面的思維導圖。

    常用設(shè)計模式
    六大設(shè)計原則
看了上面的文章,相信大家對設(shè)計模式的好處有了直觀的了解,也大致掌握了單例模式及觀察者模式。

設(shè)計模式都是經(jīng)過了大量的代碼、軟件實踐而總結(jié)出來的優(yōu)秀的組織實踐方案。每種設(shè)計模式都有它的適應(yīng)場景,有的場景也會使用多種設(shè)計模式。只有了解了更多的設(shè)計模式,掌握各個設(shè)計模式自己的適應(yīng)場景,才能更好的為我們所用。

但是過早的優(yōu)化不一定是好事或者不是必須的,有時候我們可以一開始并不去優(yōu)化,等到某個應(yīng)用場景下出現(xiàn)了代碼組織混亂、需要額外擴展等問題,我們再優(yōu)化重構(gòu),以防過早優(yōu)化導致的不必要性或者只是增加了代碼不必要的復雜性。就像redux,如果一個頁面組件與組件之間有數(shù)據(jù)共享、需要在任意組件內(nèi)部拿到某個數(shù)據(jù)、任意一個組件中某個行為導致的數(shù)據(jù)變化需要通知到所有用到的地方,那么這個時候可以使用redux,一些簡單的表單頁面或者展示頁完全可以不用redux。

看到這里不容易,最后給大家講一個笑話輕松一下:
從前有只麋鹿,它在森林里玩兒,不小心走丟了。  
于是它給它的好朋友長頸鹿打電話:“喂…我迷路辣?!? 
長頸鹿聽見了回答說:“喂~我長頸鹿辣~”

參考:曾探《javascript設(shè)計模式與開發(fā)實踐》


iKcamp官網(wǎng):http://www.ikcamp.com

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

iKcamp原創(chuàng)新書《移動Web前端高效開發(fā)實戰(zhàn)》已在亞馬遜、京東、當當開售。

iKcamp最新活動

報名地址:http://www.huodongxing.com/event/5409924174200

“天天練口語”小程序總榜排名第四、教育類排名第一的研發(fā)團隊,面對面溝通交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,130評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,315評論 1 10
  • 三、閉包和高階函數(shù) 3.1 閉包 3.1.1 變量的作用域 所謂變量的作用域,就是變量的有效范圍。通過作用域的劃分...
    梁同學de自言自語閱讀 1,551評論 0 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 面向?qū)ο缶幊?1.創(chuàng)建,使用函數(shù) var CheckObject = {checkName : function(...
    依米花1993閱讀 465評論 0 0

友情鏈接更多精彩內(nèi)容