淺談 MVC 和 MVVM 模型

MVC

那時計算機世界天地混沌,渾然一體,而後出現了一個創世者,將現實世界抽象出模型造成model,將人機交互從應用邏輯中分離造成view,而後就有了空氣、水、雞啊、蛋什麼的。
——《前端MVC變形記》

MVC 模式表明 Model-View-Controller(模型-視圖-控制器) 模式。這種模式用於應用程序的分層開發。javascript

Model(模型)

Model(模型)是應用程序中用於處理應用程序數據邏輯的部分。一般模型對象負責在數據庫中存取數據前端

Model定義了這個模塊的數據模型。在代碼中體現爲數據管理者,Model負責對數據進行獲取及存放。vue

數據不可能憑空生成的,要麼是從服務器上面獲取到的數據,要麼是本地數據庫中的數據,也有多是用戶在UI上填寫的表單即將上傳到服務器上面存放,因此須要有數據來源。既然Model是數據管理者,則天然由它來負責獲取數據。java

MVC容許在不改變視圖的狀況下改變視圖對用戶輸入的響應方式,用戶對View的操做交給了Controller處理,在Controller中響應View的事件調用Model的接口對數據進行操做,一旦Model發生變化便通知相關視圖進行更新。react

這裏咱們把須要用到的數值變量封裝在Model中,並定義了add、sub、getVal三種操做數值方法。數據庫

var myapp = {}; // 建立這個應用對象

myapp.Model = function() {
    var val = 0;

    this.add = function(v) {
        if (val < 100) val += v;
    };

    this.sub = function(v) {
        if (val > 0) val -= v;
    };

    this.getVal = function() {
        return val;
    };

    /* 觀察者模式 */
    var self = this, 
        views = [];

    this.register = function(view) {
        views.push(view);
    };

    this.notify = function() {
        for(var i = 0; i < views.length; i++) {
            views[i].render(self);
        }
    };
};

Model和View之間使用了觀察者模式,View事先在此Model上註冊,進而觀察Model,以便更新在Model上發生改變的數據。json

View(視圖)

View(視圖)是應用程序中處理數據顯示的部分。一般視圖是依據模型數據建立的。segmentfault

View,視圖,簡單來講,就是咱們在界面上看見的一切。 後端

view和controller之間使用了策略模式,這裏View引入了Controller的實例來實現特定的響應策略,好比這個栗子中按鈕的 click 事件:前端框架

myapp.View = function(controller) {
    var $num = $('#num'),
        $incBtn = $('#increase'),
        $decBtn = $('#decrease');

    this.render = function(model) {
        $num.text(model.getVal() + 'rmb');
    };

    /*  綁定事件  */
    $incBtn.click(controller.increase);
    $decBtn.click(controller.decrease);
};

若是要實現不一樣的響應的策略只要用不一樣的Controller實例替換便可。

Controller(控制器)

Controller(控制器)是應用程序中處理用戶交互的部分。一般控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據。

Controller是MVC中的數據和視圖的協調者,也就是在Controller裏面把Model的數據賦值給View來顯示(或者是View接收用戶輸入的數據而後由Controller把這些數據傳給Model來保存到本地或者上傳到服務器)。

myapp.Controller = function() {
    var model = null,
        view = null;

    this.init = function() {
        /* 初始化Model和View */
        model = new myapp.Model();
        view = new myapp.View(this);

        /* View向Model註冊,當Model更新就會去通知View啦 */
        model.register(view);
        model.notify();
    };

    /* 讓Model更新數值並通知View更新視圖 */
    this.increase = function() {
        model.add(1);
        model.notify();
    };

    this.decrease = function() {
        model.sub(1);
        model.notify();
    };
};

這裏咱們實例化View並向對應的Model實例註冊,當Model發生變化時就去通知View作更新,這裏用到了觀察者模式。

當咱們執行應用的時候,使用Controller作初始化:

(function() {
    var controller = new myapp.Controller();
    controller.init();
})();

通信

各部分之間的通訊方式以下,全部通信都是單向的 。

  1. View 傳送指令到 Controller
  2. Controller 完成業務邏輯後,要求 Model 改變狀態
  3. Model 將新的數據發送到 View,用戶獲得反饋

bg2015020105.png
接受用戶指令時,MVC 能夠分紅兩種方式:
一種是經過 View 接受指令,傳遞給 Controller。
bg2015020106.png

另外一種是直接經過controller接受指令。
bg2015020107.png

MVC模式的業務邏輯主要集中在Controller,而前端的View其實已經具有了獨立處理用戶事件的能力,當每一個事件都流經Controller時,這層會變得十分臃腫。並且MVC中View和Controller通常是一一對應的,捆綁起來表示一個組件,視圖與控制器間的過於緊密的鏈接讓Controller的複用性成了問題。

MVVM

MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行爲抽象化,讓咱們將視圖 UI 和業務邏輯分開。固然這些事 ViewModel 已經幫咱們作了,它能夠取出 Model 的數據同時幫忙處理 View 中因爲須要展現內容而涉及的業務邏輯。

Model

咱們能夠把Model稱爲數據層,由於它僅僅關注數據自己,不關心任何行爲(格式化數據由View的負責),這裏能夠把它理解爲一個相似json的數據對象。

var data = {
    val: 0
};

View

指的是所看到的頁面,和MVC/MVP不一樣的是,MVVM中的View經過使用模板語法來聲明式的將數據渲染進DOM,當ViewModelModel進行更新的時候,會經過數據綁定更新到View

div id="myapp">
    <div>
        <span>{{ val }}rmb</span>
    </div>
    <div>
        <button v-on:click="sub(1)">-</button>
        <button v-on:click="add(1)">+</button>
    </div>
</div>

ViewModel

mvvm模式的核心,它是鏈接view和model的橋樑。它有兩個方向:

  1. Model轉化成View,即將後端傳遞的數據轉化成所看到的頁面。實現的方式是:數據綁定。
  2. View轉化成Model,即將所看到的頁面轉化成後端的數據。實現的方式是:DOM 事件監聽。這兩個方向都實現的,咱們稱之爲數據的雙向綁定。
new Vue({
    el: '#myapp',
    data: data,
    methods: {
        add(v) {
            if(this.val < 100) {
                this.val += v;
            }
        },
        sub(v) {
            if(this.val > 0) {
                this.val -= v;
            }
        }
    }
});

總結:
在MVVM的框架下視圖View和模型Model是不能直接通訊的。它們經過ViewModel來通訊,ViewModel一般要實現一個observer觀察者,當數據發生變化,ViewModel可以監聽到數據的這種變化,而後通知到對應的視圖作自動更新,而當用戶操做視圖,ViewModel也能監聽到視圖的變化,而後通知數據作改動,這實際上就實現了數據的雙向綁定。而且MVVM中的ViewViewModel能夠互相通訊。

總體來看,比MVC/MVP精簡了不少,不只僅簡化了業務與界面的依賴,還解決了數據頻繁更新(之前用jQuery操做DOM很繁瑣)的問題。由於在MVVM中,View不知道Model的存在,ViewModelModel也察覺不到View,這種低耦合模式可使開發過程更加容易,提升應用的可重用性。

MVVM流程圖以下:
bg2015020110.png

Vue數據雙向綁定原理

數據綁定

雙向數據綁定,能夠簡單而不恰當地理解爲一個模版引擎,可是會根據數據變動實時渲染。
c507025c2f9a5c9e0c44.png
不一樣的MVVM框架中,實現雙向數據綁定的技術有所不一樣。目前一些主流的前端框架實現數據綁定的方式大體有如下幾種:

  • 數據劫持 (Vue)
  • 發佈-訂閱模式 (Knockout、Backbone)
  • 髒值檢查 (Angular)

Vue採用數據劫持&發佈-訂閱模式的方式,經過ES5提供的 Object.defineProperty() 方法來劫持(監控)各屬性的 gettersetter ,並在數據(對象)發生變更時通知訂閱者,觸發相應的監聽回調。而且,因爲是在不一樣的數據上觸發同步,能夠精確的將變動發送給綁定的視圖,而不是對全部的數據都執行一次檢測。

要實現Vue中的雙向數據綁定,大體能夠劃分三個模塊:Observer、Compile、Watcher,如圖:
eeb9bbbdf001b43d6002 (1).png

  • Observer 數據監聽器
    負責對數據對象的全部屬性進行監聽(數據劫持),監聽到數據發生變化後通知訂閱者。
  • Compiler 指令解析器
    掃描模板,並對指令進行解析,而後綁定指定事件。
  • Watcher 訂閱者
    關聯Observer和Compile,可以訂閱並收到屬性變更的通知,執行指令綁定的相應操做,更新視圖。Update()是它自身的一個方法,用於執行Compile中綁定的回調,更新視圖。

數據劫持

通常對數據的劫持都是經過Object.defineProperty方法進行的,Vue中對應的函數爲 defineReactive ,其普通對象的劫持的精簡版代碼以下:

var foo = {
  name: 'vue',
  version: '2.0'
}

function observe(data) {
    if (!data || typeof data !== 'object') {
        return
    }
    // 使用遞歸劫持對象屬性
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    })
}

function defineReactive(obj, key, value) {
     // 監聽子屬性 好比這裏data對象裏的 'name' 或者 'version'
     observe(value)

    Object.defineProperty(obj, key, {
        get: function reactiveGetter() {
            return value
        },
        set: function reactiveSetter(newVal) {
            if (value === newVal) {
                return
            } else {
                value = newVal
                console.log(`監聽成功:${value} --> ${newVal}`)
            }
        }
    })
}

observe(foo)
foo.name = 'angular' // 「監聽成功:vue --> angular」複製代碼

上面完成了對數據對象的監聽,接下來還須要在監聽到變化後去通知訂閱者,這須要實現一個消息訂閱器 Dep ,Watcher經過 Dep 添加訂閱者,當數據改變便觸發 Dep.notify() ,Watcher調用本身的 update() 方法完成視圖更新。


推薦閱讀:
【專題:JavaScript進階之路】
ES6 Promise
JavaScript之深刻理解閉包
ES6 尾調用和尾遞歸
Git經常使用命令小結

參考:http://www.javashuo.com/article/p-ovruuryr-gh.html


我是Cloudy,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!
相關文章
相關標籤/搜索