浅谈 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,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
我的笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎你们指出,也欢迎你们一块儿交流前端各类问题!
相关文章
相关标签/搜索