作者:曉飛
本文原創(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最新活動
與
“天天練口語”
小程序總榜排名第四荆忍、教育類排名第一的研發(fā)團隊格带,面對面溝通交流。