浅谈-MVC-和-MVVM-模型

50次阅读

共计 5007 个字符,预计需要花费 13 分钟才能阅读完成。

MVC

那时计算机世界天地混沌,浑然一体,然后出现了一个创世者,将现实世界抽象出模型形成 model,将人机交互从应用逻辑中分离形成 view,然后就有了空气、水、鸡啊、蛋什么的。
——《前端 MVC 变形记》

MVC 模式代表 Model-View-Controller(模型 - 视图 - 控制器)模式。这种模式用于应用程序的分层开发。

Model(模型)

Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中 存取数据

Model 定义了这个模块的数据模型。在代码中体现为数据管理者,Model 负责对数据进行获取及存放。

数据不可能凭空生成的,要么是从服务器上面获取到的数据,要么是本地数据库中的数据,也有可能是用户在 UI 上填写的表单即将上传到服务器上面存放,所以需要有数据来源。既然 Model 是数据管理者,则自然由它来负责获取数据。

MVC 允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对 View 的操作交给了 Controller 处理,在 Controller 中响应 View 的事件调用 Model 的接口对数据进行操作,一旦 Model 发生变化便通知相关视图进行更新。

这里我们把需要用到的数值变量封装在 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 上发生改变的数据。

View(视图)

View(视图)是应用程序中处理 数据显示 的部分。通常视图是依据模型数据创建的。

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,用户得到反馈


接受用户指令时,MVC 可以分成两种方式:
一种是通过 View 接受指令,传递给 Controller。

另一种是直接通过 controller 接受指令。

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,当 ViewModel 对 Model 进行更新的时候,会通过数据绑定更新到 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 中的 View 和 ViewModel 可以互相通信。

整体来看,比 MVC/MVP 精简了很多,不仅仅简化了业务与界面的依赖,还解决了数据频繁更新(以前用 jQuery 操作 DOM 很繁琐)的问题。因为在 MVVM 中,View 不知道 Model 的存在,ViewModel 和 Model 也察觉不到 View,这种低耦合模式可以使开发过程更加容易,提高应用的可重用性。

MVVM 流程图如下:

Vue 数据双向绑定原理

数据绑定

双向数据绑定,可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。

不同的 MVVM 框架中,实现双向数据绑定的技术有所不同。目前一些主流的前端框架实现数据绑定的方式大致有以下几种:

  • 数据劫持 (Vue)
  • 发布 - 订阅模式 (Knockout、Backbone)
  • 脏值检查 (Angular)

Vue 采用数据劫持 & 发布 - 订阅模式的方式,通过 ES5 提供的 Object.defineProperty() 方法来劫持(监控)各属性的 gettersetter,并在数据(对象)发生变动时通知订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。

要实现 Vue 中的双向数据绑定,大致可以划分三个模块:Observer、Compile、Watcher,如图:

  • 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 常用命令小结

参考:https://juejin.im/post/593021272f301e0058273468


我是 Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!

正文完
 0