JS前端中的設(shè)計(jì)模式和使用場景示例詳解
引言
相信大家在日常學(xué)習(xí)和工作中都多多少少聽說/了解/使用過 設(shè)計(jì)模式,我們都知道,使用恰當(dāng)?shù)脑O(shè)計(jì)模式可以優(yōu)化我們的代碼,那你是否知道對于前端開發(fā)哪些 設(shè)計(jì)模式 是日常工作經(jīng)常用到或者必須掌握的呢?本文我將帶大家一起學(xué)習(xí)下前端常見的設(shè)計(jì)模式以及它們的 使用場景!!!
本文主講:
- 策略模式
- 代理模式
適合人群:
- 前端人員
- 設(shè)計(jì)模式小白/想知道如何在項(xiàng)目中使用設(shè)計(jì)模式
策略模式
策略模式的定義是:定義一系列的算法,把它們一個(gè)個(gè)封裝起來,并且使它們可以相互替換。從定義不難看出,策略模式是用來解決那些一個(gè)功能有多種方案、根據(jù)不同條件輸出不同結(jié)果且條件很多的場景,而這些場景在我們工作中也經(jīng)常遇到,接下來我將用幾個(gè)例子來展示策略模式在哪里用以及如何用。
1.績效考核
假如我們有這么一個(gè)需求,需要根據(jù)員工的績效考核給員工發(fā)放年終獎(jiǎng)(分為A/B/C/D四個(gè)等級 分別對應(yīng)基礎(chǔ)獎(jiǎng)金的1/2/3/4倍),我們很容易就寫出這樣的代碼
//level 評級 basicBonus 基礎(chǔ)獎(jiǎng)金 const computeBonus(level, basicBonus) = () => { if(level === 'A') { return basicBonus * 1; } else if(level === 'B') { return basicBonus * 2; } else if(level === 'C') { return basicBonus * 3; } else if(level === 'D') { return basicBonus * 4; } } computeBonus('A', 1000);//1000
我們發(fā)現(xiàn),以上的代碼可以輕松實(shí)現(xiàn)我們的需求,但是這些代碼存在什么問題呢?
computedBonus
方法十分臃腫,包含太多if-else
- 拓展性差,后續(xù)如果想要更改評級或者規(guī)則都需要進(jìn)入該函數(shù)內(nèi)部調(diào)整。
- 復(fù)用性差。
那策略模式是怎么解決這些問題的呢?我們都知道,設(shè)計(jì)模式的核心之一就是將可變的和不可變的部分抽離分裝,那我們根據(jù)這個(gè)原則來修改我們的代碼,其中可變的就是如何使用這些算法(多少個(gè)評級),不變的是算法的內(nèi)容(評級對應(yīng)的獎(jiǎng)金),下面就是改變后的代碼
//定義策略類 const strategies = { 'A': function(basicBonus) { return basicBonus * 1; }, 'B': function(basicBonus) { return basicBonus * 2; }, 'C': function(basicBonus) { return basicBonus * 3; }, 'D': function(basicBonus) { return basicBonus * 4; }, } //使用策略類 const computeBonus = (level, basicBonus) { return strategies[level](basicBonus); } computeBouns('A', 1000);//1000
從上面可以看出,我們將每種情況都單獨(dú)弄成一個(gè)策略,然后根據(jù)傳入評級和獎(jiǎng)金計(jì)算年終獎(jiǎng),這樣我們的computeBonus
方法代碼量大大減少,也不用冗雜的if-else
分支,同時(shí),如果我們想要改變規(guī)則,只需要在strategies
中添加對應(yīng)的策略,增加了代碼的健壯性
2.表單驗(yàn)證
我們?nèi)粘5墓ぷ髦?,不可避免地需要做表單相關(guān)的業(yè)務(wù),畢竟這是前端最初始的職能之一。而表單繞不開表單驗(yàn)證,那接下來我將帶大家看看策略模式在表單中如何使用。
需求: 假設(shè)我們有一個(gè)登錄業(yè)務(wù),提交表單前需要做驗(yàn)證,驗(yàn)證規(guī)則如下:1.用戶名稱不能為空,2.密碼不能少于6位,3.手機(jī)格式要正確。
我們很容易寫出以下代碼
const verifyForm = (formData) => { if(formData.userName == '') { console.log('用戶名不能為空'); return false }; if(formData.password.length < 6) { console.log('密碼長度不能小于6位'); return false; } if(( !/(^1[3|5|8][0-9]{9}$)/.test(formData.phone)) { console.log('手機(jī)格式錯(cuò)誤'); return false } }
顯然,這樣也可以完成表單校驗(yàn)的功能,但是這樣寫同樣存在著上面說的問題,接下來,我們看下用策略模式如何改寫
//編寫策略對象 const strategies = { isEmpty: function(value, error) { if(value === '' { return error; }) }, minLength: function(value, len, error) { if(value.length < len { return error; }) }, isPhone: function(value, error) { if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ return errorMsg; } }; } //接下來我們編寫實(shí)現(xiàn)類 用于生成對應(yīng)的策略實(shí)例 class Validator { controustor(cache) { this.cache = cache || []; //保存校驗(yàn)規(guī)則 }; add(dom, rule, error) { const arr = rule.splt(':');//分離參數(shù) this.cache.push(function(){ // 把校驗(yàn)的步驟用空函數(shù)包裝起來,并且放入 cache const strategy = arr.shift(); // 用戶挑選的 strategy arr.unshift( dom.value ); // 把 input 的 value 添加進(jìn)參數(shù)列表 arr.push( errorMsg ); // 把 error 添加進(jìn)參數(shù)列表 return strategies[ strategy ].apply( dom, ary ); }); }; start() { for ( let i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ var msg = validatorFunc(); // 開始校驗(yàn),并取得校驗(yàn)后的返回信息 if ( msg ){ // 如果有確切的返回值,說明校驗(yàn)沒有通過 return msg; } } } } //編寫完策略對象和實(shí)例類后我們就可以看看如何使用了 const validataFunc = function(){ let validator = new Validator(); // 創(chuàng)建一個(gè) validator 對象 /***************添加一些校驗(yàn)規(guī)則****************/ validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' ); validator.add( registerForm.password, 'minLength:6', '密碼長度不能少于 6 位' ); validator.add( registerForm.phoneNumber, 'isMobile', '手機(jī)號碼格式不正確' ); var errorMsg = validator.start(); // 獲得校驗(yàn)結(jié)果 return errorMsg; // 返回校驗(yàn)結(jié)果 } var registerForm = document.getElementById( 'registerForm' ); registerForm.onsubmit = function(){ var errorMsg = validataFunc(); // 如果 errorMsg 有確切的返回值,說明未通過校驗(yàn) if ( errorMsg ){ alert ( errorMsg ); return false; // 阻止表單提交 } };
這樣,我們就用策略模式將需求改好了,之后如果我們的校驗(yàn)規(guī)則改變了,修改起來也是很方便的,比如:
validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' ); // 改成:
validator.add( registerForm.userName, 'minLength:10', '用戶名長度不能小于 10 位' );
而且,我們也可以給文本框添加多個(gè)校驗(yàn)規(guī)則,只需要修改下策略對象以及策略方法即可!大大地增強(qiáng)了代碼地健壯性。
策略模式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
- 避免多重條件選擇語句(
if-else
) - 具有可拓展性,可獨(dú)立抽離封裝,避免重復(fù)復(fù)制粘貼
缺點(diǎn):
- 增加很多策略類或者策略對象,但是這其實(shí)不算什么大缺點(diǎn)
- 比起直接編寫業(yè)務(wù)代碼需要思考策略對象以及其他細(xì)節(jié)
代理模式
代理模式是為一個(gè)對象提供一個(gè)代用品或占位符,以便控制對它的訪問。代理模式應(yīng)該是我們?nèi)粘S玫奖容^多的設(shè)計(jì)模式了(我們?nèi)粘9ぷ髦胁恢挥X就會用到代理模式,可能你沒發(fā)現(xiàn)而已)。
代理模式分為保護(hù)代理(用于控制不同權(quán)限的對象對目標(biāo)對象的訪問)和虛擬代理(把開銷很大的對象或者操作延遲到真正需要的時(shí)候再去創(chuàng)建 類比引入時(shí)動態(tài)引入)兩種,但是前端基本不用到保護(hù)代理,或者說很難實(shí)現(xiàn)保護(hù)代理,所以大部分情況下我們用的都是虛擬代理,接下來我主要也是講虛擬代理!
舉個(gè)例子,加入A想要給C送情書,但是A沒有直接把情書交給C,而是讓B代為傳送情書,那么B就是代理,他的職責(zé)就是替A做事,這個(gè)就是最簡單的代理模式,接下來我們還是老樣子,邊寫需求邊講解
1.圖片懶加載:
相信大家對于圖片懶加載都不陌生吧,他可以在我們加載出目標(biāo)圖片前預(yù)加載占位圖片,避免空白區(qū)域影響體驗(yàn),那我們很容易就能寫出下面的代碼
const lazyImage = (function() { let imgNode = document.createElement('img'); document.body.appendChild(imgNode); let image = new Image; image.onload = function() { imgNode.src = image.src;//在這里設(shè)置圖片的真正路由 }; return { setSrc: function(src) { imgNode.src = '....'//預(yù)加載本地圖片; image.src = src } } })() lazyImage.setSrc('https://olddog.jpg');//加載真正的圖片
我們看上面的代碼,也可以完成預(yù)加載的功能,但是這樣的代碼存在著什么樣的問題呢
- 違反了單一職責(zé)原則,而且耦合度太高,如果后期我們不需要懶加載了,或者需要根據(jù)判斷條件判斷是否懶加載,就不得不去動lazyImage的代碼
接下來,我們就用代理模式來改寫一下這個(gè)例子
const lazyImage = (function() { let imageNode = document.createElement('img'); document.body.appendChild(imageNode); return { setSrc: function(src) { imageNode.src = src;//設(shè)置目標(biāo)src } } })() //代理函數(shù) const proxyImage = (function() { let image = new Image; image.onload = function() { myImage.setSrc(this.src); } return { setSrc: function(src) { myImage.setSrc('....')//預(yù)加載本地圖片 img.src = src } } })() proxyImage.setSrc('https://olddog.jpg');//使用代理加載
我們觀察用代理模式寫的代碼,發(fā)現(xiàn)我們將預(yù)加載的邏輯轉(zhuǎn)移到了代理函數(shù)中,這樣有啥好處呢
- 如果后期不需要預(yù)加載了,只需要取消代理,即將
proxyImage.setSrc(...)
改成lazyImage.setSrc(...)
- 代理函數(shù)的使用方式和原函數(shù)一模一樣,使用者不需要知道代理的實(shí)現(xiàn)細(xì)節(jié)也能使用
不知道大家有沒有發(fā)現(xiàn),代理函數(shù)和原函數(shù)有一部分相似的邏輯和操作,只是代理函數(shù)的功能更多,這其實(shí)也是代理模式的特征之一,代理函數(shù)在保證實(shí)現(xiàn)原函數(shù)的基本功能的前提下實(shí)現(xiàn)更多功能,這樣即使使用者不清楚邏輯也能直接使用,而且后期改動成本很低,只需要改回原函數(shù)的使用即可!!
2.緩存代理
設(shè)想一下,如果現(xiàn)在要你寫一個(gè)簡單的求積函數(shù),你會怎么寫
const mult = function() { let result = 1; for(let i = 0, len = arguments.length; i < len; i++) { result *= arguments[i]; } return result } mult(1, 2, 3);//6
我們來看一下上面的代碼有啥缺點(diǎn),上面的代碼雖然實(shí)現(xiàn)了求積,但是如果我們mult(1,2,3)
之后再去mult(1,2,3)
,那么系統(tǒng)還是會再計(jì)算一遍,這樣無疑是性能浪費(fèi),那么我們就能用代理模式來改寫:
const proxyMult = (function() { let cache = {};//緩存計(jì)算結(jié)果 return function() { const args = Array.prototype.join.call( arguments, ','); if(args in cache) { return cache[args] } return cache[args] = mult.apply(this.arguments) } })(); proxyMult(1,2,3);//6 proxyMult(1, 2, 3);//輸出6 但是不會重新計(jì)算
可以看到,我們用代理模式改寫后避免了重復(fù)運(yùn)算的浪費(fèi),這只是一種情景,還有其他相似情景,比如我們分頁請求數(shù)據(jù),可以使用相似的思路,避免對同頁的數(shù)據(jù)重復(fù)請求,這在工作中非常有用!!
總結(jié)
我們?nèi)粘9ぷ髦羞€有很多地方用到代理,比如代理合并請求(間斷性合并而不是全部合并,減少服務(wù)器壓力)、惰性加載或創(chuàng)建申請資源等等,而什么時(shí)候使用代理其實(shí)不需要提前花很多精力去思考,當(dāng)我們寫著寫著發(fā)現(xiàn)可以抽離使用代理模式的時(shí)候再去使用也不遲。由于文章篇幅有限,本文就先講解策略模式和代理模式,后續(xù)將繼續(xù)更新其他實(shí)用的設(shè)計(jì)模式,喜歡的小伙伴可以點(diǎn)個(gè)贊和關(guān)注一下,有啥問題可以評論區(qū)一起學(xué)習(xí)交流!
以上就是JS前端中的設(shè)計(jì)模式和使用場景示例詳解的詳細(xì)內(nèi)容,更多關(guān)于前端設(shè)計(jì)模式場景的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript編程通過Matlab質(zhì)心算法定位學(xué)習(xí)
這篇文章主要為大家介紹了JavaScript編程中通過Matlab質(zhì)心算法來定位的算法學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10微信小程序 使用picker封裝省市區(qū)三級聯(lián)動實(shí)例代碼
這篇文章主要介紹了微信小程序 使用picker封裝省市區(qū)三級聯(lián)動實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10一篇文章學(xué)會jsBridge的運(yùn)行機(jī)制
JSBridge是一座用JavaScript搭建起來的橋,搭建這座橋的目的也很簡單,讓native可以調(diào)用web的js代碼,讓web可以 “調(diào)用” 原生的代碼。本文主要通過分析源碼講解jsBridge的運(yùn)行機(jī)制,感興趣的朋友一起來看看吧2021-09-09