当下是 MVVM 盛行的时代,从早期的 Angular 到现在的 React 和 Vue,从最初的三分天下,到现在的两虎相争,给我们的开发带来了一种前所未有的体验,告别了操作 DOM 的思维,换上了数据驱动页面的思想。
MV*
MVC
MVC 模式就是,软件分为3部分:model(数据保存) + view(视图) + controller(业务逻辑),所有通讯都是单向的。
- view 传送指令给 controller
- controller 完成业务逻辑后,要求 model 改变状态
- model 将新的数据发送给 view ,用户得到反馈
优点:
- 职责分离:模块化程度高、controller可替换、可服用行、可扩展性强
- 多视图更新:使用观察者模式,可以做到单 model 通知多视图实现数据更新
缺点:
- 测试困难,view 需要 UI 环境,因此依赖 View 的 Controller 测试相对比较困难
- 依赖强烈:view 强依赖 Model。因此 View 无法组件化
MVP
MVP 模式将 controller 改名为 presenter
- 各部分的通信都是双向的
- view 和 model 不直接通讯,都通过 presenter 传递
- view 非常薄,不部署任何业务逻辑,称为“被动视图”,即没有任何主动性,而 presenter 非常厚,所有逻辑部署在那里
优点:Presenter 便于测试、view 可组件化设计
缺点: Presenter 厚、维护困难
MVVM
MVVM 模式将 Presenter 改名为 ViewModel,和 MVP 模式基本一致,唯一的区别就是,它采用双向绑定,view 的变动,自动反应在 viewModel。
优点:
- 提升了可维护行,解决了 MVP 大量的手动同步的问题,提供双向绑定机制
- 简化了测试,同步逻辑由 Binder 处理,view 跟着 model 同时变更,所以只要 model 正确,view 就正确
缺点:
- 性能问题,对于简单的应用会造成额外的性能消耗
- 对于复杂的应用,视图状态较多,视图状态维护成本增加。
对于前端开发而言,MVVM是一个很好的设计模式。在浏览器中,路由层可以将控制权交给适当的 ViewModel,后者又可以更新并响应持续的 View,并通过一些小修改,MVVM 模式可以很好的运行在服务器端,其中的原因在于 Model 和 view 已经完全没有了依赖关系。
MVVM模式的优点
MVVM 的优点:
- 低耦合:view 可以独立于 model 变化和修改,一个 viewModel 可以绑定到不同的 view 上,当 view 变化的时候,Model 可以不变,当 Model 变化时,view 也可以不变
- 可重用性:可以把一些视图逻辑放在一个 viewModel 里面,让很多 view 重用这段视图逻辑
- 独立开发:开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计
MVVM与MVC模式的区别
- MVC 中 controller 演变成了 MVVM 的 ViewModel
- MVVM 通过数据来显示视图层而不是节点操作
- MVVM 主要解决了 MVC 中大量的 dom 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
Vue 的响应式
初始化流程
- 创建 vue 实例对象
- init 过程会初始化生命周期,初始化事件中心,初始化渲染、执行 beforeCreate 周期函数、初始化 data、props、computed、watcher、执行 created 周期函数
- 初始化后,调用 $mount 方法对 Vue 实例进行挂载(挂载的核心包括模版编译、渲染、以及更新)
- 如果没有在 vue 实例上定义 render 方法,而是定义了 template,那么需要经历编译阶段,需要先将 template 字符串编译成 render function,template 字符串编译步骤如下:
- parse 正则解析 template 字符串形成 AST(抽象语法树,是源代码的抽象语法结构的树状表现形式)
- optimize 标记静态节点跳过 diff 算法。(diff 算法是逐层进行比对,只有同层级的节点进行比对,因此时间的复杂度只有O(n))
- generate 将 AST 转化成 render function 字符串
- 编译成render function 后,调用 $mount 的 mountComponent 方法,先执行 beforeMount 钩子函数,然后核心是实例化一个渲染 Watcher,在它的回调函数中调用 updateComponent 方法(此方法调用 render 方法生成虚拟 Node,最终调用 update 方法更新 DOM)
- 调用 render 方法将 render function 渲染成虚拟的 Node(真正的 DOM 元素非常庞大,如果频繁去做 DOM 更新,会产生一定的性能问题,而 Virtual DOM 就是用一个原生的 JavaScript 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价小很多,而且修改属性也很简单,还可以跨平台兼容)
- 生成虚拟 DOM 树后,将虚拟 DOM 树转换成真实的 DOM 节点,调用 update 方法,update 方法又会调用 pacth 方法把虚拟 DOM 转换成真实的 DOM 节点。
响应式流程
- 在 init 阶段,会利用 Object.defineProperty 方法,舰艇 Vue 实例的响应式数据的变化,从而实现数据挟持能力。在初始化流程中的编译阶段,当 render function 被渲染的时候,会读取 Vue 实例中和视图相关的响应式数据,此时会触发 getter 函数进行树依赖收集,此时的数据挟持功能和观察者模式实现了一个 MVVM 模式中的 Binder,之后就是正常的渲染和更新
- 当数据变化或视图导致数据变化时,会触发数据挟持的 setter 函数,setter 会通知初始化依赖收集中的 Dep 重的和视图对应的 Watcher,告知需要重新渲染视图,Watcher 就会再次通过 update 方法来更新视图。
基于 Vue 机制的简易 MVVM 实现
<input id="input" type="text">
<div id="div">
function hijack(data) {
if(typeof data !== 'object') return;
for(let key of Object.keys(data)) {
let val = data[key];
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log('hijack', val)
return val;
},
set(newVal) {
if(newVal === val) return;
console.log('hijack', newVal)
val = newVal;
input.value = newVal;
div.innerHTML = newVal
hijack(newVal)
}
})
}
}
let input = document.getElementById('input');
let div = document.getElementById('div');
let data = {input: ''};
hijack(data)
data.input = '1111111'
input.oninput = function(e) {
data.input = e.target.value
}