使用合適的設(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的鏈?zhǔn)秸{(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)個性化需求也是合理的伯顶。修改配置遠(yuǎn)比修改源代碼要簡單的多囚灼。

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

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

    實現(xiàn)單例模式的核心就是保證一個類僅有一個實例,那么意思就是當(dāng)創(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)該是同一個碎连,而不是重復(fù)新建。而且為了性能優(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)建對象和管理實例的邏輯都放在一個地方峭梳,違反了單一職責(zé)原則舰绘,我們應(yīng)該單獨新建一個用于創(chuàng)建單例的方法蹂喻,這樣我們不僅能創(chuàng)建唯一的modal實例,也能創(chuàng)建其他的捂寿,職責(zé)分開口四。

    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)系,  
    當(dāng)某個對象發(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è)計原則可以參考下面的思維導(dǎo)圖。

    常用設(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)化導(dǎo)致的不必要性或者只是增加了代碼不必要的復(fù)雜性励烦。就像redux谓着,如果一個頁面組件與組件之間有數(shù)據(jù)共享、需要在任意組件內(nèi)部拿到某個數(shù)據(jù)坛掠、任意一個組件中某個行為導(dǎo)致的數(shù)據(jù)變化需要通知到所有用到的地方赊锚,那么這個時候可以使用redux,一些簡單的表單頁面或者展示頁完全可以不用redux屉栓。

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

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


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

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

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

iKcamp最新活動

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刹枉,一起剝皮案震驚了整個濱河市叽唱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌微宝,老刑警劉巖棺亭,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蟋软,居然都是意外死亡镶摘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門岳守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凄敢,“玉大人,你說我怎么就攤上這事湿痢±苑欤” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵譬重,是天一觀的道長拒逮。 經(jīng)常有香客問我,道長害幅,這世上最難降的妖魔是什么消恍? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任岂昭,我火速辦了婚禮以现,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘约啊。我一直安慰自己邑遏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布恰矩。 她就那樣靜靜地躺著记盒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪外傅。 梳的紋絲不亂的頭發(fā)上纪吮,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天俩檬,我揣著相機與錄音,去河邊找鬼碾盟。 笑死棚辽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冰肴。 我是一名探鬼主播屈藐,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼熙尉!你這毒婦竟也來了联逻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤检痰,失蹤者是張志新(化名)和其女友劉穎包归,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铅歼,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡箫踩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谭贪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片境钟。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖俭识,靈堂內(nèi)的尸體忽然破棺而出慨削,到底是詐尸還是另有隱情,我是刑警寧澤套媚,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布缚态,位于F島的核電站,受9級特大地震影響堤瘤,放射性物質(zhì)發(fā)生泄漏玫芦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一本辐、第九天 我趴在偏房一處隱蔽的房頂上張望桥帆。 院中可真熱鬧,春花似錦慎皱、人聲如沸老虫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祈匙。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夺欲,已是汗流浹背跪帝。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留些阅,地道東北人歉甚。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像扑眉,于是被迫代替她去往敵國和親纸泄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

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