前端的MVC,近幾年一直很火,你們也都紛紛討論着,因而乎,抽空總結一下這個知識點。看了些文章,結合實踐略做總結並發表一下本身的見解。 html
最初接觸MVC是後端Java的MVC架構,用一張圖來表示之——前端
MVC是一種設計模式,它將應用劃分爲3個部分:數據(模型)、展示層(視圖)和用戶交互(控制器)。換句話說,一個事件的發生是這樣的過程:
1. 用戶和應用產生交互。
2. 控制器的事件處理器被觸發。
3. 控制器從模型中請求數據,並將其交給視圖。
4. 視圖將數據呈現給用戶。
咱們不用類庫或框架就能夠實現這種MVC架構模式。關鍵是要將MVC的每部分按照職責進行劃分,將代碼清晰地分割爲若干部分,並保持良好的解耦。這樣能夠對每一個部分進行獨立開發、測試和維護。
模型用來存放應用的全部數據對象。好比,可能有一個User模型,用以存放用戶列表、他們的屬性及全部與模型有關的邏輯。
模型沒必要知道視圖和控制器的邏輯。任何事件處理代碼、視圖模板,以及那些和模型無關的邏輯都應當隔離在模型以外。
將模型的代碼和視圖的代碼混在一塊兒,是違反MVC架構原則的。模型是最應該從你的應用中解耦出來的部分。
當控制器從服務器抓取數據或建立新的記錄時,它就將數據包裝成模型實例。也就是說,咱們的數據是面向對象的,任何定義在這個數據模型上的函數或邏輯均可以直接被調用。ajax
視圖層是呈現給用戶的,用戶與之產生交互。在JavaScript應用中,視圖大都是由HTML、CSS、JavaScript模板組成的。除了模板中簡單的條件語句以外,視圖不該當包含任何其餘邏輯。
將邏輯混入視圖之中是編程的大忌,這並非說MVC不容許包含視覺呈現相關的邏輯,只要這部分邏輯沒有定義在視圖以內便可。咱們將視覺呈現邏輯歸類爲「視圖助手」(helper):和視圖相關的獨立的小工具函數。
來看下面的例子,騎在視圖中包含了邏輯,這是一個範例,平時不該當這樣作:編程<div> <script> function formatDate(date) { /* ... */ } </script> ${ formateDate(this.date) } </div>在這段代碼中,咱們把formatDate()函數直接插入視圖中,這違反了MVC的原則,結果致使標籤看上去像大雜燴同樣不可維護。能夠將視覺呈現邏輯剝離出來放入試圖助手中,正以下面的代碼就避免了這個問題,可讓這個應用的結構知足MVC。小程序
// helper.js var helper = {}; helper.formateDate(date) { /* ... */ }; // template.html <div> ${ helper.formate(this.date) } </div>此外,全部視覺呈現邏輯都包含在helper變量中,這是一個命名空間,能夠防止衝突並保持代碼清晰、可擴展。後端
控制器是模型和視圖之間的紐帶。控制器從視圖獲取事件和輸入,對它們(極可能包含模型)進行處理,並相應地更新視圖。當頁面加載時,控制器會給視圖添加事件監聽,好比監聽表單提交或按鈕點擊。而後,當用戶和你的應用產生交互時,控制器中的事件觸發器就開始工做了。
咱們用簡單的jQuery代碼來實現控制器——設計模式var Controller = {}; (Controller.users = function($) { var nameClick = function() { /* ... */ }; // 在頁面加載時綁定事件監聽 $(function() { $('#view .name').click(nameClick); }); })(jQuery);
2> app.js服務器
1.維護的model是todo實例的列表,這樣,咱們對增長記錄、刪改某一條記錄,都要從新渲染整個列表,這樣,致使性能的拙劣行。固然,改進的方式是對每個實例進行對應dom的綁定。 2.這裏的View中,咱們看到其中參雜了一些顯示邏輯,顯然,我提倡這樣去作,而非在js中去控制業務邏輯。然而,咱們在實際開發的過程中,咱們必然涉及到複雜的顯示邏輯,這樣,咱們能夠向以前所說的那樣,利用單獨編寫顯示邏輯helper,這與MVC的設計思想並不違背,確保高維護性及擴展性。 3.這裏有關模型todos的業務邏輯,並無嚴格抽象出來,而是寫入對應的事件當中。
接下來,看看其餘優秀的框架如何去作的。架構
3、前端MVC框架併發
相信你們都聽過MVC、MVP、MVVM了,三者的簡單定義——
(1)MVC: 模型-視圖-控制器(Model View Controller) (2)MVP: 模型-視圖-表現類(Model-View-Presenter) (3)MVVM:模型-視圖-視圖模型(Model-View-ViewModel)
它們三者的發展過程是MVC->MVP->MVVM,咱們分別來看這三者——
1> Ember.js(MVC)
先看看項目總體文件架構——
會發現,主要是有controller、model、router,先引入index.html中的模板(一樣使用的是Handlebars)——
會發現,模板代碼添加了一些晦澀的屬性標籤。對於Ember.js的使用,咱們須要建立一個Ember應用程序實例(app.js文件中)——
window.Todos = Ember.Application.create();
1. 模板文件的模板名稱data-template-name="todos"對應的路由模板即是Todos.TodosRoute; 2. 對該路由顯示指定對應模板的數據模型。固然對這裏的數據模型(即上面的model屬性)一樣進行了硬綁定(即對應的todo.js)——
Todos.todo = DS.Model.extend({ title: DS.attr('string'), isCompleted: DS.attr('boolean'), saveWhenCompletedChanged: function() { this.save(); }.observes('isCompleted') });
3. 對該路由一樣可以指定對應的controller(上面的setController屬性)。這裏主要偵聽對hash改變,對數據進行過濾操做。
下面咱們看一看對Controller的定義,固然存在必定的硬綁定(潛規則)——todos-controller.js
會發現上面的這個controller是針對model集合的,對單條model記錄的controller,放在todo-controller.js文件中——
對這些方法的調用,看一看對應的模板文件就知道了——
<ul id="todo-list"> {{#each filteredTodos itemController="todo"}} <li {{bind-attr class="isCompleted:completed isEditing:editing"}}> {{#if isEditing}} {{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}} {{else}} {{input type="checkbox" class="toggle" checked=isCompleted}} <label {{action "editTodo" on="doubleClick"}}>{{title}}</label> <button {{action "removeTodo"}} class="destroy"></button> {{/if}} </li> {{/each}} </ul>
會發現,紅色標註的部分,正是咱們在todo-controler.js中定義的事件。還會發現,Ember.js封裝了一些事件屬性,如——
focus-out insert-newline escape-press doubleClick
到這兒,Ember.js的內容就簡單介紹完了,總結一下——
1. 程序的加載入口是rounter(即app.TemplatenameRouter),來指定對應的model及controller。路由是負責顯示模板,加載數據,以及管理應用程序的狀態。 2. 程序的交互入口是controller,這裏麪包含兩個類型的controller,一個是對應model集合的controller,一個是對應model的controller。二者各司其職,增長了代碼的可維護性。
Ember.js是典型的MVC(這裏有別於MVP、MVVM的設計模式類)框架,還有一個比較典型的MVC框架即是Angular.js,和Ember.js的設計思想大體相同。
從Ember.js的應用,咱們能夠理解MVC的特色——MVC的View直接與Model打交道,Controller僅僅起一個「橋樑」做用,它負責把View的請求轉發給Model,再負責把Model處理結束的消息通知View。Controller就是一個消息分發器。不傳遞數據(業務結果),Controller是用來解耦View和Model的,具體一點說,就是爲了讓UI與邏輯分離(界面與代碼分離)。
2>Backbone.js(MVP)
依舊先看一下文件架構——
相對於Ember.js和Angular.js,它的模板比較清爽——
這是因爲添加了Presenter的緣由,事件的綁定及頁面view的變化,所有由Presenter去作。
這裏存在一個model集合的概念,即這裏的collection.js——
app-view.js生成應用的一個Presenter實例(new AppView()),並由該實例來綁定事件,並控制集合todos的變化(用戶經過view產生交互來觸發),一旦todos發生變化,來觸發對應的view變化。一樣的,這裏的todo-view.js乾的是一樣一件事,只不過針對的是model單個對象。
從Backbone.js的應用,咱們能夠理解MVP的特色——Presenter直接調用Model的接口方法,當Model中的數據發生改變,通知Presenter進行對應的View改變。從而使得View再也不與Model產生交互。
3> Knockout.js(MVVM)
先看看它的頁面——
會發現不少data-bind屬性,先無論它,咱們在看看ViewModel的定義——
會發現,視圖View中的data-bind屬性值正是ViewModel實例的對應方法,這彷佛看起來很像是視圖助手helper要作的事情。其實否則,這裏的ViewModel,顧名思義,是對View的一次抽象,即對View再提取其對應的模型。
MVVM的特色以下——
1. ViewModel是model和View的中間接口 2. ViewMode提供View與Model數據之間的命令,即這裏的data-bind的值,ViewModel中的方法 3. UI的渲染均由ViewModel經過命令來控制
4、前端MVC模式與傳統開發模式的對比
傳統的開發模式,大多基於事件驅動的編碼組織,舉個例子——
$('#update').click(function(e) { // 1.事件處理程序 e.preventDefault(); // 2.獲取對應的model的屬性值 var title = $('#text').val(); // 3.調用業務邏輯 $.ajax({ url : '/xxx', type : 'POST', data : { title : title, completed : false }, success : function(data) { // 4.對data進行處理,並進行對應的dom渲染 }, error: function() { // 4.錯誤處理 } }); });
優化一些,咱們能夠分離事件處理程序和業務邏輯,在這裏,就不延伸舉例了。總之,傳統的開發模式,並無分層的概念,即沒有model、view、controller。好的方面是咱們能夠對單獨的業務邏輯進行抽取並單獨測試。並對這個部分代碼進行復用及封裝。壞的方面,當應用變得愈來愈複雜的時候,就會顯得代碼凌亂,維護性日益變差。
有同窗可能會說,還能夠結合面向對象、單命名空間的方式,讓代碼看起來更加優雅,更具可維護性。可是仍是沒有辦法有效去分離UI邏輯的頻繁變化(這裏僅僅針對富應用程序)。