核心要點
設計模式通常被整合到流行的框架中。例如,模型-視圖-控制器(MVC)設計模式無處不在。在JavaScript中,很難將框架與設計模式分離。通常,特定的框架會帶有其自己對這種設計模式的解釋。框架帶有觀點,每個框架都強迫你以某種方式思考。
現代框架決定了MVC模式的具體實現方式。當所有解釋都不同時,這會令人困惑,從而增加噪音和混亂。當任何代碼庫採用多個框架時,就會產生令人沮喪的混亂。我心中的問題是,有沒有更好的方法?
MVC模式適用於客戶端框架,但現代框架會發生變化。當今的現代化會隨著時間的推移而消亡。在這種情況下,我想探索替代方案,看看一點紀律能帶我們到哪裡。
閱讀現代JavaScript,隨時了解JavaScript不斷變化的世界! 閱讀本書 MVC模式本身可以追溯到幾十年前。這使其成為一個值得您投入編程技能的設計模式。 MVC模式是一個可以獨立存在的模式。問題是,這能帶我們走多遠?
首先,我想消除這個常見的誤解:設計模式不是框架。設計模式是一種解決代碼問題的嚴謹方法。這需要一定的技能水平,並將責任放在程序員身上。設計模式分離關注點並促進編寫乾淨的代碼。
框架則不同,因為它不必遵循任何設計模式。區分框架和模式的一種方法是尋找好萊塢原則。好萊塢原則就是:“別打電話給我們,我們會打電話給你。”任何時候都有一個依賴項決定你何時使用它,它就是一個框架。框架很像好萊塢,你不能決定做什麼或如何做。事實上,開發人員就像演員一樣,在被要求行動時遵循劇本。
避免客戶端框架有很多很好的理由:
MVC設計模式起源於20世紀70年代和80年代的施樂Smalltalk研究項目。這是一個經受了時間考驗的用於前端圖形用戶界面的模式。該模式來自桌面應用程序,但已被證明對Web應用程序也很有效。
其核心是MVC設計模式是關於關注點的清晰分離。其目的是使解決方案清晰易懂。任何想要進行特定更改的程序員都可以輕鬆找到正確的位置。
企鵝!可愛又毛茸茸的,是地球上最可愛的生物之一。事實上,它們非常可愛,有17種不同的企鵝,並非所有企鵝都生活在南極洲的環境中。
是時候做一個企鵝的演示了!我將展示一個頁面上顯示幾種物種的牌組。為此,我想使用MVC設計模式和一點紀律。我將使用極限編程方法來使用單元測試和簡單的方法來解決手頭的問題。最後,您應該能夠翻閱幾隻企鵝,每隻企鵝都有自己的數據和個人資料圖片。
在本例結束時,您應該已經學習了足夠多的知識,可以在純JavaScript中使用MVC設計模式。該模式本身非常易於測試,因此可以預期良好的單元測試。
出於跨瀏覽器兼容性的原因,我將在此演示中堅持使用ES5。使用經過驗證的語言特性與這種永久的設計模式相結合是有意義的。
你準備好了嗎?讓我們拭目以待。
演示將包含三個主要部分:控制器、視圖和模型。每個部分都有其自身的關注點和需要解決的問題。
下面是其外觀的可視化效果:
PenguinController處理事件,是視圖和模型之間的中介。它會計算出用戶執行操作(例如,單擊按鈕或按下一個鍵)時會發生什麼。客戶端特定的邏輯可以放在控制器中。在一個有很多事情要做的更大的系統中,您可以將其分解成模塊。控制器是事件的入口點,也是視圖和數據之間唯一的調解者。
PenguinView關心DOM。 DOM是您用來進行HTML操作的瀏覽器API。在MVC中,除了視圖之外,沒有其他部分關心更改DOM。視圖可以附加用戶事件,但將事件處理問題留給控制器。視圖的主要指令是更改用戶在屏幕上看到的狀態。對於此演示,視圖將使用純JavaScript進行DOM操作。
PenguinModel關心數據。在客戶端JavaScript中,這意味著Ajax。 MVC模式的一個優點是您現在有一個用於服務器端Ajax調用的單一位置。這使得不熟悉該解決方案的其他程序員更容易上手。此設計模式中的模型只關心來自服務器的JSON或對象。
一種反模式是違反這種內在的關注點分離。例如,模型一定不能關心HTML。視圖一定不能關心Ajax。控制器必須充當調解者,而無需擔心實現細節。
我發現使用這種模式時,開發人員一開始懷有良好的意圖,但會洩露關注點。將所有內容都變成Web組件並最終變成一團糟是很誘人的。重點放在功能和麵向用戶的關注點上。但是,功能關注點與功能關注點不同。
在編程中,我喜歡的是對功能關注點進行清晰的分離。每個單獨的編程問題都會得到一種一致的解決方法。當您閱讀代碼時,這使其更易於理解。其目的是編寫易於理解的代碼,以便其他人也能做出積極的貢獻。
如果沒有一個您可以看到和触摸的真實示例,那它就不是一個很好的演示。因此,不用多說,下面是一個CodePen,展示了企鵝的演示:
查看SitePoint (@SitePoint)在CodePen上的筆A Demo of Penguins。
說得夠多了,是時候寫代碼了。
視圖和模型是控制器使用的兩個組件。控制器在其構造函數中包含完成工作所需的所有組件:
<code>var PenguinController = function PenguinController(penguinView, penguinModel) { this.penguinView = penguinView; this.penguinModel = penguinModel; }; </code>
構造函數使用控制反轉並以此方式註入模塊。此模式使您可以注入滿足高級契約的任何組件。將其視為一種抽象代碼與實現細節的好方法。此模式使您能夠使用純JavaScript編寫乾淨的代碼。
然後,用戶事件會以這種方式連接和處理:
<code>PenguinController.prototype.initialize = function initialize() { this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this); }; PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) { var target = e.currentTarget; var index = parseInt(target.dataset.penguinIndex, 10); this.penguinModel.getPenguin(index, this.showPenguin.bind(this)); }; </code>
請注意,此事件使用當前目標來獲取存儲在DOM中的狀態。在這種情況下,DOM會告訴您有關其當前狀態的所有信息。 DOM的當前狀態是用戶在瀏覽器上看到的內容。您可以將狀態數據存儲在DOM本身中,只要控制器不更改狀態即可。
觸發事件後,控制器會獲取數據並說明接下來會發生什麼。 this.showPenguin()回調很有趣:
<code>PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) { var penguinViewModel = { name: penguinModelData.name, imageUrl: penguinModelData.imageUrl, size: penguinModelData.size, favoriteFood: penguinModelData.favoriteFood }; penguinViewModel.previousIndex = penguinModelData.index - 1; penguinViewModel.nextIndex = penguinModelData.index + 1; if (penguinModelData.index === 0) { penguinViewModel.previousIndex = penguinModelData.count - 1; } if (penguinModelData.index === penguinModelData.count - 1) { penguinViewModel.nextIndex = 0; } this.penguinView.render(penguinViewModel); }; </code>
控制器計算每個企鵝的索引並告訴視圖呈現它。它從模型中獲取數據並將其轉換為視圖理解和關心的對象。
以下是顯示企鵝時快樂路徑的單元測試:
<code>var PenguinController = function PenguinController(penguinView, penguinModel) { this.penguinView = penguinView; this.penguinModel = penguinModel; }; </code>
PenguinViewMock與真實實現具有相同的契約。這使得編寫單元測試和進行斷言成為可能。斷言來自Node斷言,也存在於Chai斷言中。這使您可以編寫可以在Node和瀏覽器上運行的測試。
請注意,控制器不關心實現細節。它使用視圖提供的契約,例如this.render()。這就是編寫乾淨代碼所需的紀律。控制器可以相信每個組件都能做到它所說的那樣。這增加了透明度,使代碼更易於閱讀。
視圖只關心DOM元素和連接事件,例如:
<code>PenguinController.prototype.initialize = function initialize() { this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this); }; PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) { var target = e.currentTarget; var index = parseInt(target.dataset.penguinIndex, 10); this.penguinModel.getPenguin(index, this.showPenguin.bind(this)); }; </code>
當它更改用戶看到的狀態時,實現如下所示:
<code>PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) { var penguinViewModel = { name: penguinModelData.name, imageUrl: penguinModelData.imageUrl, size: penguinModelData.size, favoriteFood: penguinModelData.favoriteFood }; penguinViewModel.previousIndex = penguinModelData.index - 1; penguinViewModel.nextIndex = penguinModelData.index + 1; if (penguinModelData.index === 0) { penguinViewModel.previousIndex = penguinModelData.count - 1; } if (penguinModelData.index === penguinModelData.count - 1) { penguinViewModel.nextIndex = 0; } this.penguinView.render(penguinViewModel); }; </code>
請注意,其主要關注點是將視圖模型數據轉換為HTML並更改狀態。第二個是連接點擊事件並讓控制器充當入口點。狀態更改後,事件處理程序會附加到DOM。此技術一次性處理事件管理。
為了測試這一點,我們可以驗證元素是否已更新並更改了狀態:
<code>var PenguinViewMock = function PenguinViewMock() { this.calledRenderWith = null; }; PenguinViewMock.prototype.render = function render(penguinViewModel) { this.calledRenderWith = penguinViewModel; }; // Arrange var penguinViewMock = new PenguinViewMock(); var controller = new PenguinController(penguinViewMock, null); var penguinModelData = { name: 'Chinstrap', imageUrl: 'http://chinstrapl.jpg', size: '5.0kg (m), 4.8kg (f)', favoriteFood: 'krill', index: 2, count: 5 }; // Act controller.showPenguin(penguinModelData); // Assert assert.strictEqual(penguinViewMock.calledRenderWith.name, 'Chinstrap'); assert.strictEqual(penguinViewMock.calledRenderWith.imageUrl, 'http://chinstrapl.jpg'); assert.strictEqual(penguinViewMock.calledRenderWith.size, '5.0kg (m), 4.8kg (f)'); assert.strictEqual(penguinViewMock.calledRenderWith.favoriteFood, 'krill'); assert.strictEqual(penguinViewMock.calledRenderWith.previousIndex, 1); assert.strictEqual(penguinViewMock.calledRenderWith.nextIndex, 3); </code>
這解決了所有主要問題,更改狀態和連接事件。但是,數據從哪裡來?
在MVC中,所有模型關心的都是Ajax。例如:
<code>var PenguinView = function PenguinView(element) { this.element = element; this.onClickGetPenguin = null; }; </code>
請注意,模塊XMLHttpRequest被注入到構造函數中。這是一種讓其他程序員知道此模型需要哪些組件的方法。如果模型需要的不僅僅是簡單的Ajax,您可以使用更多模塊來表示這一點。此外,使用單元測試,我可以注入與原始模塊具有完全相同契約的模擬。
是時候根據索引獲取企鵝了:
<code>PenguinView.prototype.render = function render(viewModel) { this.element.innerHTML = '<h3>' + viewModel.name + '</h3>' + '<img alt="' + viewModel.name + '" src="'%20+%20viewModel.imageUrl%20+%0A%20%20%20%20%20%20'">' + '<p><b>Size:</b> ' + viewModel.size + '</p>' + '<p><b>Favorite food:</b> ' + viewModel.favoriteFood + '</p>' + '<a href="https://www.php.cn/link/f0b875eb6cff6fd5f491e6b6521c7510"> ' data-penguin-index="' + viewModel.previousIndex + '">Previous</a> ' + '<a href="https://www.php.cn/link/f0b875eb6cff6fd5f491e6b6521c7510"> ' data-penguin-index="' + viewModel.nextIndex + '">Next</a>'; this.previousIndex = viewModel.previousIndex; this.nextIndex = viewModel.nextIndex; // Wire up click events, and let the controller handle events var previousPenguin = this.element.querySelector('#previousPenguin'); previousPenguin.addEventListener('click', this.onClickGetPenguin); var nextPenguin = this.element.querySelector('#nextPenguin'); nextPenguin.addEventListener('click', this.onClickGetPenguin); nextPenguin.focus(); }; </code>
這指向一個端點並從服務器獲取數據。我們可以通過使用單元測試模擬數據來測試這一點:
<code>var ElementMock = function ElementMock() { this.innerHTML = null; }; // Stub functions, so we can pass the test ElementMock.prototype.querySelector = function querySelector() { }; ElementMock.prototype.addEventListener = function addEventListener() { }; ElementMock.prototype.focus = function focus() { }; // Arrange var elementMock = new ElementMock(); var view = new PenguinView(elementMock); var viewModel = { name: 'Chinstrap', imageUrl: 'http://chinstrap1.jpg', size: '5.0kg (m), 4.8kg (f)', favoriteFood: 'krill', previousIndex: 1, nextIndex: 2 }; // Act view.render(viewModel); // Assert assert(elementMock.innerHTML.indexOf(viewModel.name) > 0); assert(elementMock.innerHTML.indexOf(viewModel.imageUrl) > 0); assert(elementMock.innerHTML.indexOf(viewModel.size) > 0); assert(elementMock.innerHTML.indexOf(viewModel.favoriteFood) > 0); assert(elementMock.innerHTML.indexOf(viewModel.previousIndex) > 0); assert(elementMock.innerHTML.indexOf(viewModel.nextIndex) > 0); </code>
如您所見,模型只關心原始數據。這意味著使用Ajax和JavaScript對象。如果您不清楚純JavaScript中的Ajax,有一篇文章包含更多信息。
對於任何紀律,獲得保證所做的工作都很重要。 MVC設計模式並不規定如何解決問題。設計模式為您提供了一套廣泛的邊界,使您能夠編寫乾淨的代碼。這使您免受依賴壓迫。
對我來說,這意味著為每個用例提供一套完整的單元測試。測試提供了有關代碼如何有用的指導。這使其對任何想要進行特定更改的程序員來說都是開放和誘人的。
隨意查看整套單元測試。我認為這將幫助您理解這種設計模式。每個測試都是針對特定用例的;將其視為細粒度的關注點。單元測試幫助您獨立地考慮每個編碼問題並解決此問題。 MVC中這種功能關注點的分離通過每個單元測試都體現出來。
企鵝的演示只包含了展示MVC有多麼有用的基本可行概念。但是,您可以迭代許多改進:
當然,我的讀者,您可以進一步改進此演示。這些只是一些想法,您可以展示這種設計模式的強大功能。
我希望您能看到MVC設計模式和一點紀律能帶您到哪裡。一個好的設計模式會在不礙事的同時促進編寫乾淨的代碼。它會在解決手頭問題時讓您專注於任務。它會讓您成為一個更好、更高效的程序員。
在編程中,其目的是緊密關注手頭的問題,同時消除冗餘。編程的藝術是一次解決一個問題。在MVC中,這意味著一次解決一個功能性問題。
作為一名開發人員,很容易相信自己是邏輯的,並且不處理情緒。事實是,當您一次遇到太多問題時,您會感到沮喪。這是我們所有人必須應對的正常人類反應。事實上,沮喪會以負面方式影響代碼質量。當這種感覺抓住您並主導您的工作時,它不再是關於邏輯了。隨著解決方案承擔更多風險和復雜的依賴關係,這可能會令人沮喪。
我喜歡的是專注於單一關注點。一次解決一個問題並獲得積極的反饋。這樣,您就可以保持專注、高效並避免無意義的事情。
本文由Vildan Softic同行評審。感謝所有SitePoint的同行評審者,使SitePoint的內容達到最佳狀態!
JavaScript中的模型-視圖-控制器(MVC)設計模式至關重要,因為它有助於以簡潔和系統的方式組織代碼。它將應用程序的關注點分成三個相互關聯的組件。模型處理數據和業務邏輯,視圖管理數據的顯示,控制器處理用戶輸入。這種分離允許高效的代碼管理、更輕鬆的調試和改進的可擴展性。
MVC模式通過隔離職責來增強代碼的可讀性和可維護性。 MVC模式的每個組件都有不同的作用。這種分離意味著開發人員可以處理各個組件而不會影響其他組件。它還使查找和修復錯誤、更新功能或重構代碼變得更容易,因為一個組件中的更改不會影響其他組件。
MVC模式中的模型負責管理數據和業務邏輯。它從數據庫檢索數據、操作數據並更新數據。模型獨立於用戶界面,不直接與視圖或控制器交互。相反,當其狀態發生變化時,它會向它們發送通知。
MVC模式中的視圖負責將數據顯示給用戶。它從模型接收數據並以用戶友好的格式呈現數據。視圖不直接與模型交互。相反,它從控制器接收更新。
MVC模式中的控制器充當模型和視圖之間的中介。它處理用戶輸入並相應地更新模型和視圖。當用戶與視圖交互時,控制器會解釋輸入並對模型進行必要的更改。它還會更新視圖以反映這些更改。
MVC模式通過分離關注點來增強可擴展性。這種分離允許開發人員修改或擴展一個組件而不會影響其他組件。例如,如果您需要更改數據顯示方式,您可以修改視圖而無需接觸模型或控制器。這種模塊化使得隨著時間的推移更容易擴展和發展您的應用程序。
是的,MVC模式可以與各種JavaScript框架一起使用,例如AngularJS、Ember.js和Backbone.js。這些框架提供了一種實現MVC模式的結構化方法,使構建複雜的應用程序更容易。
由於JavaScript的動態特性,在JavaScript中實現MVC模式可能具有挑戰性。它需要對語言有很好的理解,並需要仔細規劃以確保模型、視圖和控制器正確分離並正確交互。此外,管理這些組件之間的更新可能很複雜。
MVC模式通過允許不同的開發人員同時處理不同的組件來支持團隊開發。例如,一個開發人員可以處理模型,而另一個開發人員可以處理視圖。這種關注點的分離不僅提高了生產力,而且還減少了由於代碼重疊而導致衝突或錯誤的可能性。
是的,MVC模式可以用於開發移動應用程序。它提供了一種結構化的應用程序開發方法,使管理複雜的移動應用程序更容易。許多流行的移動開發框架,如React Native和Ionic,都支持MVC模式。
以上是香草JavaScript中的MVC設計模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!