網(wǎng)上已經(jīng)有很多開發(fā)者在討論。我不知道你怎么想,但對我來說:設(shè)計模式是我個人覺得可以更好解決問題的一種方案。

這意味著什么?如果你開發(fā)的項目的" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > web前端的設(shè)計模式是什么?

web前端的設(shè)計模式是什么?

時間:2024-02-08 02:30:01 | 來源:網(wǎng)站運營

時間:2024-02-08 02:30:01 來源:網(wǎng)站運營

web前端的設(shè)計模式是什么?:什么是設(shè)計模式?我們?yōu)槭裁葱枰獙W習設(shè)計模式?

網(wǎng)上已經(jīng)有很多開發(fā)者在討論。我不知道你怎么想,但對我來說:設(shè)計模式是我個人覺得可以更好解決問題的一種方案。

這意味著什么?如果你開發(fā)的項目的功能是固定的,永遠不會調(diào)整業(yè)務(wù),那么你就不需要使用設(shè)計模式等任何技巧。您只需要使用通常的方式編寫代碼并完成需求即可。

但是,我們的開發(fā)項目的需求是不斷變化的,這就需要我們經(jīng)常修改我們的代碼。也就是說,我們現(xiàn)在寫代碼的時候,需要為未來業(yè)務(wù)需求可能發(fā)生的變化做好準備。

這時,你會發(fā)現(xiàn)使用設(shè)計模式可以讓你的代碼更具可擴展性。

經(jīng)典的設(shè)計模式有 23 種,但并不是每一種設(shè)計模式都被頻繁使用。在這里,我介紹我最常用和最實用的 3 種設(shè)計模式。

01、策略模式

假設(shè)您目前正在從事一個電子商務(wù)商店的項目。每個產(chǎn)品都有一個原價,我們可以稱之為 originalPrice。但并非所有產(chǎn)品都以原價出售,我們可能會推出允許以折扣價出售商品的促銷活動。商家可以在后臺為產(chǎn)品設(shè)置不同的狀態(tài)。然后實際售價將根據(jù)產(chǎn)品狀態(tài)和原價動態(tài)調(diào)整。

具體規(guī)則如下:

部分產(chǎn)品已預(yù)售。為鼓勵客戶預(yù)訂,我們將在原價基礎(chǔ)上享受 20% 的折扣。

部分產(chǎn)品處于正常促銷階段。如果原價低于或等于100,則以10%的折扣出售;如果原價高于 100,則減 10 美元。

有些產(chǎn)品沒有任何促銷活動。它們屬于默認狀態(tài),以原價出售。

如果你需要寫一個getPrice函數(shù),你應(yīng)該怎么寫呢?

function getPrice(originalPrice, status){ // ... return price }其實,面對這樣的問題,如果不考慮任何設(shè)計模式,最直觀的寫法可能就是使用if-else通過多個判斷語句來計算價格。

有三種狀態(tài),所以我們可以快速編寫如下代碼:

function getPrice(originalPrice, status) { if (status === 'pre-sale') { return originalPrice * 0.8 } if (status === 'promotion') { if (origialPrice <= 100) { return origialPrice * 0.9 } else { return originalPrice - 20 } } if (status === 'default') { return originalPrice }}有三個條件;然后,我們寫三個 if 語句,這是非常直觀的代碼。

但是這段代碼并不友好。

首先,它違反了單一職責原則。主函數(shù) getPrice 做了太多的事情。這個函數(shù)不易閱讀,也容易出現(xiàn)bug。如果一個條件有bug,整個函數(shù)就會崩潰。同時,這樣的代碼也不容易調(diào)試。

然后,這段代碼很難應(yīng)對變化。正如我在文章開頭所說的那樣,設(shè)計模式往往會在業(yè)務(wù)邏輯發(fā)生變化時表現(xiàn)出它的魅力。

假設(shè)我們的業(yè)務(wù)擴大了,現(xiàn)在還有另一個折扣促銷:黑色星期五,折扣規(guī)則如下:

這時候怎么擴展getPrice函數(shù)呢?

看起來我們必須在 getPrice 函數(shù)中添加一個條件。

function getPrice(originalPrice, status) { if (status === 'pre-sale') { return originalPrice * 0.8 } if (status === 'promotion') { if (origialPrice <= 100) { return origialPrice * 0.9 } else { return originalPrice - 20 } } if (status === 'black-friday') { if (origialPrice >= 100 && originalPrice < 200) { return origialPrice - 20 } else if (originalPrice >= 200) { return originalPrice - 50 } else { return originalPrice * 0.8 } } if(status === 'default'){ return originalPrice }}每當我們增加或減少折扣時,我們都需要更改函數(shù)。這種做法違反了開閉原則。修改已有函數(shù)很容易出現(xiàn)新的錯誤,也會讓getPrice越來越臃腫。

那么我們?nèi)绾蝺?yōu)化這段代碼呢?

首先,我們可以拆分這個函數(shù)以使 getPrice 不那么臃腫。

function preSalePrice(origialPrice) { return originalPrice * 0.8}function promotionPrice(origialPrice) { if (origialPrice <= 100) { return origialPrice * 0.9 } else { return originalPrice - 20 }}function blackFridayPrice(origialPrice) { if (origialPrice >= 100 && originalPrice < 200) { return origialPrice - 20 } else if (originalPrice >= 200) { return originalPrice - 50 } else { return originalPrice * 0.8 }}function defaultPrice(origialPrice) { return origialPrice}function getPrice(originalPrice, status) { if (status === 'pre-sale') { return preSalePrice(originalPrice) } if (status === 'promotion') { return promotionPrice(originalPrice) } if (status === 'black-friday') { return blackFridayPrice(originalPrice) } if(status === 'default'){ return defaultPrice(originalPrice) }}經(jīng)過這次修改,雖然代碼行數(shù)增加了,但是可讀性有了明顯的提升。我們的main函數(shù)顯然沒有那么臃腫,寫單元測試也比較方便。

但是上面的改動并沒有解決根本的問題:我們的代碼還是充滿了if-else,當我們增加或減少折扣規(guī)則的時候,我們?nèi)匀恍枰薷膅etPrice。

想一想,我們之前用了這么多if-else,目的是什么?

實際上,使用這些 if-else 的目的是為了對應(yīng)狀態(tài)和折扣策略。

我們可以發(fā)現(xiàn),這個邏輯本質(zhì)上是一種映射關(guān)系:產(chǎn)品狀態(tài)與折扣策略的映射關(guān)系。

我們可以使用映射而不是冗長的 if-else 來存儲映射。比如這樣:

let priceStrategies = { 'pre-sale': preSalePrice, 'promotion': promotionPrice, 'black-friday': blackFridayPrice, 'default': defaultPrice}我們將狀態(tài)與折扣策略結(jié)合起來。那么計算價格會很簡單:

function getPrice(originalPrice, status) { return priceStrategies[status](originalPrice)}這時候如果需要增減折扣策略,不需要修改getPrice函數(shù),我們只需在priceStrategies對象中增減一個映射關(guān)系即可。

之前的代碼邏輯如下:

現(xiàn)在代碼邏輯:

這樣是不是更簡潔嗎?

其實這招就是策略模式,是不是很實用?我不會在這里談?wù)摬呗阅J降臒o聊定義。如果你想知道策略模式的官方定義,你可以自己谷歌一下。

如果您的函數(shù)具有以下特征:

判斷條件很多。

各個判斷條件下的代碼相互獨立

然后,你可以將每個判斷條件下的代碼封裝成一個獨立的函數(shù),接著,建立判斷條件和具體策略的映射關(guān)系,使用策略模式重構(gòu)你的代碼。

02、發(fā)布-訂閱模式

這是我們在項目中經(jīng)常使用的一種設(shè)計模式,也經(jīng)常出現(xiàn)在面試中。

現(xiàn)在,我們有一個天氣預(yù)報系統(tǒng):當極端天氣發(fā)生時,氣象站會發(fā)布天氣警報。建筑工地、船舶和游客將根據(jù)天氣數(shù)據(jù)調(diào)整他們的日程安排。

一旦氣象站發(fā)出天氣警報,他們會做以下事情:

如果,我們被要求編寫可用于通知天氣警告的代碼,你會想怎么做?

編寫天氣警告函數(shù)的常用方法可能是這樣的:

function weatherWarning(){ buildingsite.stopwork() ships.mooring() tourists.canceltrip()}這是一種非常直觀的寫法,但是這種寫法有很多不好的地方:

耦合度太高。建筑工地、船舶和游客本來應(yīng)該是分開的,但現(xiàn)在它們被置于相同的功能中。其中一個對象中的錯誤可能會導致其他對象無法工作。顯然,這是不合理的。

違反開閉原則。如果有新的訂閱者加入,那么我們只能修改weatherWarning函數(shù)。

造成這種現(xiàn)象的原因是氣象站承擔了主動告知各單位的責任。這就要求氣象站必須了解每個需要了解天氣狀況的單位。

但仔細想想,其實,從邏輯上講,建筑工地、船舶、游客都應(yīng)該依靠天氣預(yù)報,他們應(yīng)該是積極的一方。

我們可以將依賴項更改為如下所示:

氣象站發(fā)布通知,然后觸發(fā)事件,建筑工地、船舶和游客訂閱該事件。

氣象站不需要關(guān)心哪些對象關(guān)注天氣預(yù)警,只需要直接觸發(fā)事件即可。然后需要了解天氣狀況的單位主動訂閱該事件。

這樣,氣象站與訂閱者解耦,訂閱者之間也解耦。如果有新的訂閱者,那么它只需要直接訂閱事件,而不需要修改現(xiàn)有的代碼。

當然,為了完成這個發(fā)布-訂閱系統(tǒng),我們還需要實現(xiàn)一個事件訂閱和分發(fā)系統(tǒng)。

可以這樣寫:

const EventEmit = function() { this.events = {}; this.on = function(name, cb) { if (this.events[name]) { this.events[name].push(cb); } else { this.events[name] = [cb]; } }; this.trigger = function(name, ...arg) { if (this.events[name]) { this.events[name].forEach(eventListener => { eventListener(...arg); }); } };};我們之前的代碼,重構(gòu)以后變成這樣:

let weatherEvent = new EventEmit()weatherEvent.on('warning', function () { // buildingsite.stopwork() console.log('buildingsite.stopwork()')})weatherEvent.on('warning', function () { // ships.mooring() console.log('ships.mooring()')})weatherEvent.on('warning', function () { // tourists.canceltrip() console.log('tourists.canceltrip()')})weatherEvent.trigger('warning')如果你的項目中存在多對一的依賴,并且每個模塊相對獨立,那么你可以考慮使用發(fā)布-訂閱模式來重構(gòu)你的代碼。

事實上,發(fā)布訂閱模式應(yīng)該是我們前端開發(fā)者最常用的設(shè)計模式。

element.addEventListener('click', function(){ //...})// this is also publish-subscribe pattern

03、代理模式

現(xiàn)在我們的頁面上有一個列表:

<ul id="container"> <li>Jon</li> <li>Jack</li> <li>bytefish</li> <li>Rock Lee</li> <li>Bob</li> </ul>我們想給頁面添加一個效果:每當用戶點擊列表中的每個項目時,都會彈出一條消息:Hi, I'm ${name}

我們將如何實現(xiàn)此功能?

大致思路是給每個li元素添加一個點擊事件。

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Proxy Pattern</title></head><body> <ul id="container"> <li>Jon</li> <li>Jack</li> <li>bytefish</li> <li>Rock Lee</li> <li>Bob</li> </ul> <script> let container = document.getElementById('container') Array.prototype.forEach.call(container.children, node => { node.addEventListener('click', function(e){ e.preventDefault() alert(`Hi, I'm ${e.target.innerText}`) }) })</script></body></html>這種方法可以滿足要求,但這樣做的缺點是性能開銷,因為每個 li 標簽都綁定到一個事件。如果列表中有數(shù)千個元素,我們是否綁定了數(shù)千個事件?

如果我們仔細看這段代碼,可以發(fā)現(xiàn)當前的邏輯關(guān)系如下:

每個 li 都有自己的事件處理機制。但是我們發(fā)現(xiàn)不管是哪個li,其實都是ul的成員。我們可以將li的事件委托給ul,讓ul成為這些 li 的事件代理。

這樣,我們只需要為這些 li 元素綁定一個事件。

let container = document.getElementById('container') container.addEventListener('click', function (e) { console.log(e) if (e.target.nodeName === 'LI') { e.preventDefault() alert(`Hi, I'm ${e.target.innerText}`) } }) 這實際上是代理模式。

代理模式是本體不直接出現(xiàn),而是讓代理解決問題。

在上述情況下,li 并沒有直接處理點擊事件,而是將其委托給 ul。

現(xiàn)實生活中,明星并不是直接出來談生意,而是交給他們的經(jīng)紀人,也就是明星的代理人。

代理模式的應(yīng)用非常廣泛,我們來看另一個使用它的案例。

假設(shè)我們現(xiàn)在有一個計算函數(shù),參數(shù)是字符串,計算比較耗時。同時,這是一個純函數(shù)。如果參數(shù)相同,則函數(shù)的返回值將相同。

function compute(str) { // Suppose the calculation in the funtion is very time consuming console.log('2000s have passed') return 'a result'}現(xiàn)在需要給這個函數(shù)添加一個緩存函數(shù):每次計算后,存儲參數(shù)和對應(yīng)的結(jié)果。在接下來的計算中,會先從緩存中查詢計算結(jié)果。

你會怎么寫代碼?

當然,你可以直接修改這個函數(shù)的功能。但這并不好,因為緩存并不是這個功能的固有特性。如果將來您不需要緩存,那么,您將不得不再次修改此功能。

更好的解決方案是使用代理模式。

const proxyCompute = (function (fn){ // Create an object to store the results returned after each function execution. const cache = Object.create(null); // Returns the wrapped function return function (str) { // If the cache is not hit, the function will be executed if ( !cache[str] ) { let result = fn(str); // Store the result of the function execution in the cache cache[str] = result; } return cache[str] }})(compute)這樣,我們可以在不修改原函數(shù)技術(shù)的 情況下為其擴展計算函數(shù)。

這就是代理模式,它允許我們在不改變原始對象本身的情況下添加額外的功能。

好了,就分享到這里了。你最常用的是哪幾種設(shè)計模式呢?歡迎在評論中留言分享!

關(guān)鍵詞:模式,設(shè)計,端的

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關(guān)閉