MVC
MVC 是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。
在没有了解到MVC之前,我们写项目的时候会把所有的内容都写到同一个JS文件中,在实现了多个页面后我们发现这些页面有很多重复的东西,而且每个页面内容十分冗杂,对后续的维护更新带来了比较大的麻烦。
为了解决上面的问题,前端开发者使用MVC架构模式对页面代码进行优化。
MVC,即Model-View-Controller,分别对应着一个页面的数据层,视图层,逻辑层。
- 最简单的MVC通信方式:
- View传递信息给Controller。
- Controller完成逻辑后,传递信息给Model改变数据。
- Model将新的数据发送给View,修改界面。
-
MVC架构模式在实际的使用中,有更加灵活的方式:
BackboneJS框架使用的就是MVC架构模式,但是它在MVC通信的方式上更加地灵活
- 用户可以向 View 发送指令(DOM 事件),再由 View 直接要求 Model 改变状态。
- 用户也可以直接向 Controller 发送指令(改变 URL 触发 hashChange 事件),再由 Controller 发送给 View。
- Controller 非常薄,只起到路由的作用,而 View 非常厚,业务逻辑都部署在 View。所以,Backbone 索性取消了 Controller,只保留一个 Router(路由器) 。
MVC的特点
优点
-
耦合性低
M、V、C三个模块相互之间的影响非常地小,当对其中一个模块进行维护升级、数据搬运等工作时,对其他的模块影响极小。
-
重用性高
MVC允许使用各种不同样式的视图来访问同一个服务器端的代码。只需要改变View的实现方式即可,而无需对数据层,逻辑层进行改动。
-
部署快、可维护性高
使用MVC模式使开发时间得到相当大的缩减。因为模块的分离,所以开发者可以主力攻坚其中一个模块,而不需要多头兼顾。而且由于分离了视图层和逻辑层,也更易于维护。
缺点
-
定义模糊
MVC的概念非常模糊,每个开发者可能对它的理解都不尽相同。
-
不适合中小型规模项目
由于MVC整体比较复杂,对于中小型项目可能反而会使他们更加笨重。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
Model&View
这里有一个可以对数值进行加减操作的组件:上面显示数值,两个按钮可以对数值进行加减操作,操作后的数值会更新显示。
我们将依照这个“栗子”,尝试用JavaScript实现简单的具有MVC/MVP/MVVM模式的Web应用。
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;
};
};复制代码
View
View作为视图层,主要负责数据的展示。
myapp.View = function() {
/* 视图元素 */
var $num = $('#num'),
$incBtn = $('#increase'),
$decBtn = $('#decrease');
/* 渲染数据 */
this.render = function(model) {
$num.text(model.getVal() + 'rmb');
};
};复制代码
现在通过Model&View完成了数据从模型层到视图层的逻辑。但对于一个应用程序,这远远是不够的,我们还需要响应用户的操作、同步更新View和Model。于是,在MVC中引入了控制器controller,让它来定义用户界面对用户输入的响应方式,它连接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。
MVC
那时计算机世界天地混沌,浑然一体,然后出现了一个创世者,将现实世界抽象出模型形成model,将人机交互从应用逻辑中分离形成view,然后就有了空气、水、鸡啊、蛋什么的。
——《前端MVC变形记》
上个世纪70年代,美国施乐帕克研究中心,就是那个发明图形用户界面(GUI)的公司,开发了Smalltalk编程语言,并开始用它编写图形界面的应用程序。
到了Smalltalk-80这个版本的时候,一位叫Trygve Reenskaug的工程师为Smalltalk设计了MVC(Model-View-Controller)这种架构模式,极大地降低了GUI应用程序的管理难度,而后被大量用于构建桌面和服务器端应用程序。
如图,实线代表方法调用,虚线代表事件通知。
MVC允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。
Model
Model层用来存储业务的数据,一旦数据发生变化,模型将通知有关的视图。
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和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
控制器是模型和视图之间的纽带,MVC将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工作了。
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();
})();复制代码
可以明显感觉到,MVC模式的业务逻辑主要集中在Controller,而前端的View其实已经具备了独立处理用户事件的能力,当每个事件都流经Controller时,这层会变得十分臃肿。而且MVC中View和Controller一般是一一对应的,捆绑起来表示一个组件,视图与控制器间的过于紧密的连接让Controller的复用性成了问题,如果想多个View共用一个Controller该怎么办呢?这里有一个解决方案,使用MVP