MVVM【vue 知识汇点1】

127 阅读5分钟

当下是 MVVM 盛行的时代,从早期的 Angular 到现在的 React 和 Vue,从最初的三分天下,到现在的两虎相争,给我们的开发带来了一种前所未有的体验,告别了操作 DOM 的思维,换上了数据驱动页面的思想。

MV*

MVC

MVC 模式就是,软件分为3部分:model(数据保存) + view(视图) + controller(业务逻辑),所有通讯都是单向的。

  1. view 传送指令给 controller
  2. controller 完成业务逻辑后,要求 model 改变状态
  3. model 将新的数据发送给 view ,用户得到反馈

优点:

  1. 职责分离:模块化程度高、controller可替换、可服用行、可扩展性强
  2. 多视图更新:使用观察者模式,可以做到单 model 通知多视图实现数据更新

缺点:

  1. 测试困难,view 需要 UI 环境,因此依赖 View 的 Controller 测试相对比较困难
  2. 依赖强烈:view 强依赖 Model。因此 View 无法组件化

MVP

MVP 模式将 controller 改名为 presenter

  1. 各部分的通信都是双向的
  2. view 和 model 不直接通讯,都通过 presenter 传递
  3. view 非常薄,不部署任何业务逻辑,称为“被动视图”,即没有任何主动性,而 presenter 非常厚,所有逻辑部署在那里

优点:Presenter 便于测试、view 可组件化设计

缺点: Presenter 厚、维护困难

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,和 MVP 模式基本一致,唯一的区别就是,它采用双向绑定,view 的变动,自动反应在 viewModel。

优点:

  1. 提升了可维护行,解决了 MVP 大量的手动同步的问题,提供双向绑定机制
  2. 简化了测试,同步逻辑由 Binder 处理,view 跟着 model 同时变更,所以只要 model 正确,view 就正确

缺点:

  1. 性能问题,对于简单的应用会造成额外的性能消耗
  2. 对于复杂的应用,视图状态较多,视图状态维护成本增加。

对于前端开发而言,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 实现

demo

<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
}