MVC模式

前端的MVC,近幾年一直很火,你們也都紛紛討論着,因而乎,抽空總結一下這個知識點。看了些文章,結合實踐略做總結並發表一下本身的見解。  html

  最初接觸MVC是後端Java的MVC架構,用一張圖來表示之——前端

  這樣,咱們讓每個層次去關注並作好一件事情,層與層之間保持鬆耦合,咱們能夠對每個層次單獨作好測試工做。如此,咱們可讓代碼更具可維護性。
  所以,借鑑於後端的這種MVC設計思想(更多的我想是一種優秀的、通過考驗的實踐模式),針對愈來愈複雜的JavaScript應用程序,便有了猜測,咱們是否可使用MVC的設計思想,編寫出高維護性的前端程序。
 
1、MVC定義
  先來看看《 基於MVC的JavaScript Web富應用開發》對MVC的定義——
MVC是一種設計模式,它將應用劃分爲3個部分:數據(模型)、展示層(視圖)和用戶交互(控制器)。換句話說,一個事件的發生是這樣的過程:
  1. 用戶和應用產生交互。
  2. 控制器的事件處理器被觸發。
  3. 控制器從模型中請求數據,並將其交給視圖。
  4. 視圖將數據呈現給用戶。
咱們不用類庫或框架就能夠實現這種MVC架構模式。關鍵是要將MVC的每部分按照職責進行劃分,將代碼清晰地分割爲若干部分,並保持良好的解耦。這樣能夠對每一個部分進行獨立開發、測試和維護。
  而今,流行的MVC框架比比皆是,如Embejs、Angular.js、Backbone.js、Knockout.js等等——
  
  經過上圖,咱們咱們能夠清楚地瞭解Javascript MVC框架之間的特性,複雜度和學習曲線的區別,從左到右咱們瞭解到各個Javascript MVC框架是否支持數據綁定(Data Binding)、模板(Templating)和持久化等特性,從下到上MVC框架的複雜性遞增。
  固然,「咱們不用類庫或框架就能夠實現這種MVC架構模式。」所以,咱們須要對MVC的每個部分,作一個詳細的剖析——
  1> 模型——

模型用來存放應用的全部數據對象。好比,可能有一個User模型,用以存放用戶列表、他們的屬性及全部與模型有關的邏輯。
模型沒必要知道視圖和控制器的邏輯。任何事件處理代碼、視圖模板,以及那些和模型無關的邏輯都應當隔離在模型以外。
將模型的代碼和視圖的代碼混在一塊兒,是違反MVC架構原則的。模型是最應該從你的應用中解耦出來的部分。
當控制器從服務器抓取數據或建立新的記錄時,它就將數據包裝成模型實例。也就是說,咱們的數據是面向對象的,任何定義在這個數據模型上的函數或邏輯均可以直接被調用。ajax

  2> 視圖——

視圖層是呈現給用戶的,用戶與之產生交互。在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變量中,這是一個命名空間,能夠防止衝突並保持代碼清晰、可擴展。後端

  3> 控制器——

控制器是模型和視圖之間的紐帶。控制器從視圖獲取事件和輸入,對它們(極可能包含模型)進行處理,並相應地更新視圖。當頁面加載時,控制器會給視圖添加事件監聽,好比監聽表單提交或按鈕點擊。而後,當用戶和你的應用產生交互時,控制器中的事件觸發器就開始工做了。
咱們用簡單的jQuery代碼來實現控制器——設計模式

複製代碼
var Controller = {};

(Controller.users = function($) {
    var nameClick = function() {
        /* ... */
    };

    // 在頁面加載時綁定事件監聽
    $(function() {
        $('#view .name').click(nameClick);
    });
})(jQuery); 
複製代碼
  如今,咱們知道了M(Model)、V(View)、C(Controller)每一個部分的工做內容,咱們就能夠輕鬆實現屬於咱們本身的MVC應用程序了,固然,咱們徹底沒必要依賴那些流行與否的MVC框架。
  接下來,針對業界MVC的DEMO-todo的例子(項目主頁: http://todomvc.com/),簡單對比使用jQuery實現mvc及各框架對MVC的實現。
 
2、使用jQuery實現MVC
  先了解這個todo-demo——
  1. 初始化查詢列表——
  
  2.添加記錄——
  
  3.刪除記錄——
  
  4.修改記錄——
  
  5.對model集合的操做(標示那些完成、清除完成項)
  
  總體而言,這是簡單的一個富應用小程序,咱們先看看使用jQuery模擬MVC去實現之——
  1> app.html
  app.html

  2> app.js服務器

  app.js
  這樣,咱們使用jQuery實現了mvc架構的小應用程序,我再分析一下這個小demo的特色——
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)——

  index.html

  會發現,模板代碼添加了一些晦澀的屬性標籤。對於Ember.js的使用,咱們須要建立一個Ember應用程序實例(app.js文件中)——

window.Todos = Ember.Application.create();
  緊接着咱們須要渲染模板中的數據,因爲渲染模板的內容是根據路由選擇後動態獲取的模板內容,當咱們的應用程序啓動時,路由是負責顯示模板,加載數據,以及管理應用程序的狀態。
  在router.js中——
  router.js
  會發現,這裏的3個特色:
複製代碼
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

  todos-controller.js

  會發現上面的這個controller是針對model集合的,對單條model記錄的controller,放在todo-controller.js文件中——

  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——

  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的定義——

  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邏輯的頻繁變化(這裏僅僅針對富應用程序)。

相關文章
相關標籤/搜索