MVVM原理
MVVM原理
了解vue的底层和实现一个简单的MVVM,这部分主要介绍MVVM的基本概念,以及实现对数组的mvvm。
几个模型
MVC
MVC:所有通信都是单向的。
- 视图(View):用户界面。
- 控制器(Controller):业务逻辑
- 模型(Model):数据保存
接受用户指令时,MVC 可以分成两种方式:
- 通过 View 接受指令,传递给 Controller。
- 接通过controller接受指令。
Controller 非常薄,只起到路由的作用,而 View 非常厚,业务逻辑都部署在 View。
MVP
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
- 各部分之间的通信,都是双向的。
- View 与 Model 不发生联系,都通过 Presenter 传递。
- View 非常薄,不部署任何业务逻辑,称为”被动视图”(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
MVVM
mvvm 的核心是数据劫持(数据代理)、数据编译和”发布订阅模式”。
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
Vue2数据双向绑定真正实现其实靠的也是ES5中提供的Object.defineProperty,而Vue3改为使用Proxy来实现数据劫持。
数据劫持
Object.defineProperty(obj, propertyName, descriptor)用于修改属性描述符对象,作用是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。具体内容可以参考MDN。
obj,propertyName:要应用描述符的对象及其属性。descriptor:要应用的属性描述符对象。
按照vue的方法,vue会将所有的data挂载在ViewModel上,进行数据劫持简单说就是为对象增加get和set方法。
1 | |
渲染器
渲染器的作用是把虚拟 DOM 渲染为真实 DOM,当数据变化时,只需要在虚拟DOM中找到对应的VNode,并且只更新其对应的真实DOM节点。组件树中的 vnodes 必须是唯一的。
我们可以根据真实DOM构建VNode,vnode的创建就是简单的赋值操作:
1 | |
看下面的例子:
1 | |
会生成成如下虚拟DOM:
1 | |
接下来把虚拟DOM渲染为真实DOM,也就是实现一个渲染器,下面是实现的伪代码:
1 | |
对于组件,组件就是一组 DOM 元素的封装,我们其实返回的是组件对应的渲染函数:
1 | |
可以看到,组件的返回值也是虚拟 DOM,它代表组件要渲染的内容。搞清楚了组件的本质,我们就可以定义用虚拟 DOM 来描述组件 了。很简单,我们可以让虚拟 DOM 对象中的 tag 属性来存储组件函数:
1 | |
渲染器只需要判断type是不是函数就行了:
1 | |
对于mountElement,与之前的renderer函数基本相同。对于mountComponent:
1 | |
编译器
我们知道,当模板进入mvvm时首先要编译成渲染函数。模板中可能含有:
- 不带动态绑定的HTML元素
- 带动态绑定的HTML元素
- 注释
在Vue中采用带编译时信息的虚拟 DOM,当遇到不带动态绑定的HTML元素时,Vue会进行静态提升,在每次渲染时都使用这份相同的 vnode,渲染器知道新旧 vnode 在这部分是完全相同的,所以会完全跳过对它们的差异比对。
对于单个有动态绑定的元素来说,在为这些元素生成渲染函数时,Vue 在 vnode 创建调用中直接编码了每个元素所需的更新类型(style,class之类)。
在Vue中有一个概念“区块”,指内部结构是稳定的一个部分,v-if
和 v-for
指令会创建新的区块节点。每一个块都会追踪其所有带更新类型标记的后代节点
(不只是直接子节点),编译的结果会被打平为一个数组,仅包含所有动态的后代节点,这样减少了我们在虚拟
DOM 协调时需要遍历的节点数量,模板中任何的静态部分都会被高效地略过。
1 | |
对于每一个传入实例,编译器需要读取哪些节点是绑定的,创建各个节点对应的vnode,根据DOM结构创建渲染函数,并进行扁平化处理。
参考: