MVVM,即model、view、view-model,业务层、视图层以及两者的绑定层。Vue的设计参考了MVVM架构,但不完全是一个MVVM框架,因为它没有严格意义上的绑定层。
MVVM要求开发者将业务层和视图层分开:业务层负责管理数据;视图层负责页面渲染;绑定层负责双向绑定,即视图层操作通过绑定层影响业务数据,业务数据的变化通过绑定层影响视图渲染,这三层是完全解耦的:
举个例子,假如我们的页面有一个h1
标题,它要渲染的是js中变量title
的值:
<h1>这是标题</h1>
<script>
let title = '这是标题';
</script>
这里h1
的文本内容就是由view
层管理的;而model
层负责的是管理业务数据title
。现在view
和model
层都有了,下面我们就要让h1
的文本内容和title
的内容保持同步,这就是view-model
层要做的事。假设我们有这样一个xml文件:
<h1>{
{
title}}</h1>
它表示h1
的文本和变量title
的值是绑定的,当一个发生变化时,另一个应该同步变化。
如果我们能够编写一个框架,自动根据一个值,更新另一个值,那么实际上就是实现了view-model
层,我们的框架就可以称为一个MVVM框架。以后只要我们定义好视图和业务逻辑,并用一个xml文件描述两者的绑定关系,就可以实现视图和数据的同步了,这也是谷歌的Data Binding
的基本实现思路。
MVVM模式参考自MVP模式,而两者都是借鉴自经典的MVC模式。先来说说MVVM和MVP的差异。
MVP的全写是Model-View-Presenter
,即业务层、视图层和控制层。这里的控制层Presenter与view-model
层的作用是完全一样的,就是负责对视图层和业务层进行同步。但不同的是,Presenter的实现较为复杂,它要求开发者必须手动封装两者的同步逻辑,如jQuery框架就可以看做一个MVP模式的实现:
<h1></h1>
<script>
let title = '这是标题';
$('h1').text(title);
</script>
开发者需要定义当变量变化时如何更新视图,以及获取到用户输入时如何更新变量,这两者加起来就是它的Presenter层实现。这种方式也可以实现视图和业务逻辑的同步,但显然,MVP的控制层逻辑要比MVVM的声明式绑定写起来复杂得多,所以MVP模式基本上已经被MVVM代替。
而MVC是上述两个模式的鼻祖,也曾是java中最经典的模式之一,它的全写是Model-View-Controller
。model和view层与上述两个模式一致,controller层与MVP的Presenter层一样,也被称作控制层。不过,MVC中的controller功能很弱,它实际上只是一个路由层,真正实现视图与业务数据同步的是model
层的service,controller的作用就是找到对应的service而已。controller层的功能过于薄弱使得model
层变得很复杂,所以目前MVC模式已经很少使用。
Vue之所以不是一个MVVM框架,是因为它没有真正的view-model
层。在Vue中,view-model
是通过模板语法间接实现的,Vue通过编译模板,可以解析出视图层和业务层的绑定关系,通过响应式系统和虚拟DOM来实现两者的同步,详细的过程后面会加以介绍。
由于讲解Vue配置不是本文的重点,这里我们只是简单地概括一下,需要详细学习这部分内容的可以阅读Vue的官方文档:Vue官方网站。
为了简单,我们先以一个cdn版本的Vue为例:
<script type="text/javascript" src="https://unpkg.com/vue"></script>
<div id="app"></div>
<script>
let app = new Vue({
el: '#app',
data: {
title: '标题' },
template: '{
{title}}
',
methods: {
changeTitle (title) {
this.title = title; }
}
});
setTimeout(function () {
app.changeTitle('新标题');
}, 1000);
</script>
执行完script脚本对应的框架代码后,window上会新增一个构造函数Vue
,用于构建Vue实例。我们向new Vue
传入了一个配置对象,这个对象包含如el、data、template、methods
等属性,用于为Vue实例添加属性和方法。Vue会根据这些配置,生成一个可以自动生成视图的响应式的Vue组件,它不仅负责管理视图层和业务层,还负责两者的同步。
我们来简单看一下一些常用配置的作用:
#app
,那么该Vue实例生成的DOM就会直接替换id为app
的元素。import MyComponent from 'MyComponent';
Vue.component(MyComponent.name, MyComponent);
model
层的核心,相关的业务逻辑都是围绕data展开的。有了这些基本知识的铺垫,下面我们就开始详细介绍Vue的渲染过程。
我们先来打通HTML与Vue模板的关系。
下面是一个常见的Vue例子:
整个Vue应用被挂载到页面上id为app的节点上,传入的模板字符串是
。Vue会解析组件App的模板来替换该标签。在解析App的模板时发现它又引入了另一个组件MyComponent
,于是Vue继续解析MyComponent的模板,将解析结果替换到App组件模板内。全部解析之后会得到这样一个模板:
<template>
<div id="a">
<p>111</p>
<div id="comp">
<h1>222</h1>
<p>333</p>
</div>
</div>
</template>
注意,这并不是HTML代码,它仍然是Vue模板(只是这里没有定义数据绑定而已)。Vue会用纯JavaScript来描述上述结构,类似下面这样(这不是真正的内部表示,后面我们会看到Vue的真实内部表示):
这里最外部id为app的节点实际上是不存在的,Vue在生成DOM时会替换掉该元素。
我们看到,Vue用一个JavaScript对象描述了编译出来的模板(如果有数据绑定,它还会描述模板与数据的绑定关系)。接下来只需要调用原生的DOM方法依次创建这里的每一个节点,然后将它们挂载成一棵DOM子树,并插入页面,就可以得到真正的HTML。我们一般把这个树状JavaScript对象称为虚拟DOM树。下面是上面的JavaScript对象对应的DOM结构:
也就是说,通过模板可以得到真实HTML的JavaScript对象表示,然后调用原生的DOM方法,借助这个JavaScript对象去生成真实的HTML。不仅如此,在这个过程中,Vue还注入了响应式系统,可以根据数据变化自动更新视图,以及根据视图自动更新数据,下面我们来讲解具体的实现过程。
Vue的执行过程主要分两大阶段:Vue自身的初始化阶段和实例的生命周期管理阶段。
当通过