什么是MVC
MVC 是一种设计模式,它将应用划分为3 个部分:数据(模型)、展现层(视图)和用
户交互层(控制器)。换句话说,一个事件的发生是这样的过程:
1. 用户和应用产生交互。
2. 控制器的事件处理器被触发。
3. 控制器从模型中请求数据,并将其交给视图。
4. 视图将数据呈现给用户。
现在来看一个真实的例子,图1-1 展示了在Holla 中如何发送新的聊天消息。
1. 用户提交一个新的聊天消息。
2. 控制器的事件处理器被触发。
3. 控制器创建了一个新的聊天模型(Chat Model)记录。
4. 然后控制器更新视图。
5. 用户在聊天窗口看到新的聊天消息。
我们不用类库或框架就可以实现这种MVC 架构模式。关键是要将MVC 的每部分按照
职责进行划分,将代码清晰地分割为若干部分,并保持良好的解耦。这样可以对每个部
分进行独立开发、测试和维护。
下面来详细讲解MVC 中的各个组成部分。
模型
模型用来存放应用的所有数据对象。比如,可能有一个User 模型,用以存放用户列表、
它们的属性及所有与模型有关的逻辑。
模型不必知晓视图和控制器的细节,模型只需包含数据及直接和这些数据相关的逻辑。
任何事件处理代码、视图模板,以及那些和模型无关的逻辑都应当隔离在模型之外。将
模型和视图的代码混在一起,是违反MVC 架构原则的。模型是最应该从你的应用中解
耦出来的部分。
当控制器从服务器抓取数据或创建新的记录时,它就将数据包装成模型实例。也就是说,
我们的数据是面向对象的(object oriented),任何定义在这个数据模型上的函数或逻辑
都可以直接被调用。
因此,不要这样做:
var user = users["foo"];
destroyUser(user);
而要这样做:
var user = User.find("foo");
user.destroy();
第1 段代码没有命名空间的概念,并且不是面向对象的。如果在应用中定义了另一个
destroyUser() 函数的话,两个函数就会产生冲突。我们应当确保全局变量和函数的个
数尽可能少。在第2 段代码中,destroy() 函数是存放在命名空间User 的实例中的,
User 中存放了所有的记录。当然这只是理想状况,因为我们控制了全局变量的个数,更
好地避免了潜在的冲突,这种代码更加清晰,而且非常容易做继承,类似destroy() 的
这种函数就不用在每个模型中都定义一遍了。
在第3 章中我们会更深入地讲解模型,其中包含从服务器下载数据及创建对象关系映射
(ORM)。
视图
视图层是呈现给用户的,用户与之产生交互。在JavaScript 应用中,视图大都是由
HTML、CSS 和JavaScript 模板组成的。除了模板中简单的条件语句之外,视图不应当
包含任何其他逻辑。
实际上,和模型类似,视图也应当从应用的其他部分中解耦出来。视图不必知晓模型和
控制器中的细节,它们是相互独立的。将逻辑混入视图之中是编程的大忌。
这并不是说MVC 不允许包含视觉呈现相关的逻辑,只要这部分逻辑没有定义在视图之
内即可。我们将视觉呈现逻辑归类为“视图助手”(helper):和视图有关的独立的小型
工具函数。
来看下面的例子,其在视图中包含了逻辑,这是一个反例,平时不应当这样做:
// template.html
<div>
<script>
function formatDate(date) {
/* ... */
};
</script>
${ formatDate(this.date) }
</div>
在这段代码中,我们把formatDate() 函数直接插入视图中,这违反了MVC 的原则,结
果导致标签看上去像大杂烩一样不可维护。可以将视觉呈现逻辑剥离出来放入视图助手
中,正如下面的代码就避免了这个问题,可以让这个应用的结构满足MVC。
// helper.js
var helper = {};
helper.formatDate = function(){ /* ... */ };
// template.html
<div>
${ helper.formatDate(this.date) }
</div>
此外,所有视觉呈现逻辑都包含在helper 变量中,这是一个命名空间,可以防止冲突并
保持代码清晰、可扩展。
不要太在意视图和模板的细节,我们会在第5 章中有详细讲述。本小节的目的只是简单
介绍视图和MVC 架构模式之间的联系。
控制器
控制器是模型和视图之间的纽带。控制器从视图获得事件和输入,对它们(很可能包含
模型)进行处理,并相应地更新视图。当页面加载时,控制器会给视图添加事件监听,
比如监听表单提交或按钮点击。然后,当用户和你的应用产生交互时,控制器中的事件
触发器就开始工作了。
不用使用类库和框架也能实现控制器,下面这个例子就是使用简单的jQuery 代码来实现的:
var Controller = {};
// 使用匿名函数来封装一个作用域
(Controller.users = function($){
var nameClick = function(){
/* ... */
};
// 在页面加载时绑定事件监听
$(function(){
$("#view .name").click(nameClick);
});
})(jQuery);
我们创建了users 控制器,这个控制器是放在Controller 变量下的命名空间。然后,我
们使用了一个匿名函数封装了一个作用域,以避免对全局作用域造成污染。当页面加载
时,程序给视图元素绑定了click 事件的监听。
正如你所看到的,控制器并不依赖类库或框架。然而,为了构建需要的一个完整的MVC
框架,我们需要将模型从视图中抽离出来。控制器和状态的详细内容会在第4 章详细讲解。
本文节选自《基于MVC的JavaScript Web富应用开发》一书
(美)麦卡劳(MacCaw,A.)著
李晶,张散集译
图书详细信息:
本书教你如何构建先进的富应用程序,书中给出的很多优秀的工具和最佳实践都是很多程序员和工程师在工作中亟需的。