和通常文章不一樣,本文不依賴於任何現有的框架,也不試圖陷入冗長的發展歷史,而是徹底從頭開始,以一個儘量小可是能夠說明問題的案例,以此講清楚MVC這個歷史悠久、變型極多的技術理念。MVC是一種很是普及的,基礎的設計套路,在不一樣的語言社區內都有着大量的應用。理解了MVC,學習接下來的MVVM、MVP等才能成爲可能。html
MVC把一個系統的類分爲三種:Model,View和Controller。它們也遵循着職責分離原則:app
儘管MVC看起來複雜,其實用代碼表達最簡單的MVC是可能的:框架
/** 模擬 Model, View, Controller */ var M = {}, V = {}, C = {}; /** Model 負責數據 */ M.data = "hello world"; /** View 負責輸出 */ V.render = (M) => { alert(M.data); } /** Controller 搭橋*/ C.handleOnload = () => { V.render(M); } window.onload = C.handleOnload;
只要是和用戶交互的都是View,因此使用alert,或者直接輸出到console,都是一種View。函數
接下來,我但願用一個完整可是極簡的程序,來驗證MVC如何從一個平常的程序進化而來的。這個一個小型程序只不過是如此:學習
<div id="app">
<p><span id="count">1</span>this
<button id="inc">+</button> <button id="dec">-</button>
</p>
</div>
點擊按鈕會讓span加1或者減1。它簡單到你不須要分心關注,可是由足夠說明典型的html場景——就是既有數據呈現也有按鈕操做。spa
<div id="app"> <p><span id="count">0</span> <button id="inc">+</button> <button id="dec">-</button> </p> </div> <script> var counter = document.getElementById('count'); var btn1 = document.getElementById('inc'); var btn2 = document.getElementById('dec'); var count = 0; btn1.addEventListener('click',function (){ counter.innerHTML = ++count; } ) btn2.addEventListener('click',function (){ counter.innerHTML = --count; } ) </script>
當前的小型程序,全部的代碼,不管數據仍是邏輯仍是UI代碼,都是混合在一塊兒的,並無所謂的任何的職責分離。由於還小,問題不大。可是產品代碼都是從這一的基礎上逐步長大的。好比說數據就不太可能只有一個count,代碼逐步增大,一個對象的數據屬性會愈來愈多,隨着來的是操做數據的函數也會愈來愈多。同理包括用戶界面和業務邏輯。prototype
若是使用MVC的眼光來看,在此微觀模式下,其實可使用MVC的模式作代碼的職責分離。其中全部的UI元素對象,都應該放置到View類型內,其中的事件處理都是應該放到Controller內,而數據,也就是這裏的count變量和對它的操做(減一加一),應該放置到Model類內,組裝Model和View則是Controller的職責。這樣的思路下,代碼能夠改爲:設計
<div id="app"> <p><span id="count">1</span> <button id="inc">+</button> <button id="dec">-</button> </p> </div> <script> class Model{ constructor(){ this.count = 1 } inc(){ return this.count++ } dec(){ return this.count-- } } class View{ constructor(){ this.counter = document.getElementById('count') this.btn1 = document.getElementById('inc') this.btn2 = document.getElementById('dec') } setCount(c){ this.counter.innerHTML = c } attachInc(cb){ this.btn1.addEventListener('click',cb) } attachDec(cb){ this.btn2.addEventListener('click',cb) } } class Controller { constructor(){ this.m = new Model() this.v = new View() this.v.attachInc(this.onInc.bind(this)) this.v.attachDec(this.onDec.bind(this)) } onInc(){ this.v.setCount(this.m.inc()) } onDec(){ this.v.setCount(this.m.dec()) } } var c = new Controller() </script>
將應用程序劃分爲三種組件,模型 - 視圖 - 控制器(MVC)設計定義它們之間的相互做用。code
Model用於封裝數據以及對數據的處理方法。Model不依賴「View」和「Controller」,也就是說, Model 不關心它會被如何顯示或是如何被操做。Model 中數據的變化通常會經過一種刷新機制被公佈。爲此,Model須要提供被監聽的機制。
View可以實現顯示。在 View 中通常沒有程序上的邏輯。爲了實現Model變化後的響應View 上的刷新功能,View須要監聽Model的變化。
控制器(Controller)起到不一樣層面間的組織做用,用於控制應用程序的流程。它處理事件並做出響應。「事件」包括用戶的行爲和數據 Model 上的改變。
實際上,經過觀察者模式,能夠把Model的修改刷新到多個視圖中,只要視圖作一個Model變化的訂閱便可。能夠先作一個簡單的觀察者代碼:
var Event = function (sender) { this._sender = sender; this._listeners = []; } Event.prototype = { attach: function (listener) { this._listeners.push(listener); }, notify: function (args) { for (var i = 0; i < this._listeners.length; i += 1) { this._listeners[i](this._sender, args); } } };
而後把應用稍做修改,在加上一個span,其中爲第一個span的值乘以2。界面看起來是這樣:
<div id="app">
<p><span id="count">1</span>|<span id="count2">2</span>
<button id="inc">+</button> <button id="dec">-</button>
</p>
</div>
這就意味着,一個count的模型值,先作有兩個span須要消費它,所以不管何種緣由致使count的修改,兩個span都應該同步的被修改。此時咱們能夠利用Event對象,讓View訂閱count修改的時間,當count修改時,就會通知視圖,作相應的修改。完整的代碼以下:
<div id="app"> <p><span id="count">1</span>|<span id="count2">2</span> <button id="inc">+</button> <button id="dec">-</button> </p> </div> <script> class Model{ constructor(e){ this.e = e this.count = 1 } inc(){ this.count++ this.e.notify(this.count) return this.count } dec(){ this.count-- this.e.notify(this.count) return this.count } } class View{ constructor(e){ this.e = e this.e.attach(this.f.bind(this)) this.counter = document.getElementById('count') this.counter2 = document.getElementById('count2') this.btn1 = document.getElementById('inc') this.btn2 = document.getElementById('dec') } f(sender,c){ this.counter2.innerHTML = c * 2 } setCount(c){ this.counter.innerHTML = c } attachInc(cb){ this.btn1.addEventListener('click',cb) } attachDec(cb){ this.btn2.addEventListener('click',cb) } } var Event = function (sender) { this._sender = sender; this._listeners = []; } Event.prototype = { attach: function (listener) { this._listeners.push(listener); }, notify: function (args) { for (var i = 0; i < this._listeners.length; i += 1) { this._listeners[i](this._sender, args); } } }; class Controller { constructor(){ this.e = new Event() this.m = new Model(this.e) this.v = new View(this.e) this.v.attachInc(this.onInc.bind(this)) this.v.attachDec(this.onDec.bind(this)) } onInc(){ this.v.setCount(this.m.inc()) } onDec(){ this.v.setCount(this.m.dec()) } } var c = new Controller() </script>
此應用的最後一個實現,看起來更加具有了一個MVC的多個方面: