Vue.js 是如何实现 MVVM 的?

框架到底为我们做了什么?

  • 数据和视图分离,解耦(开放封闭原则)
    • 所有数据和视图不分离的,都会命中开放封闭原则
    • Vue 数据独立在 data 里面,视图在 template 中
  • 以数据驱动视图,只关心数据变化,dom 操作被封装
    • 使用原生js是直接通过操作dom来修改视图,例如 ducument.getElementById('xx').innerHTML="xxx"
    • 以数据驱动视图就是,我们只管修改数据,视图的部分由框架去帮我们修改,符合开放封闭模式

如何理解 MVVM ?

  • MVC
    • Model 数据 → View 视图 → Controller 控制器
  • MVVM
    • MVVM不算是一种创新
    • 但是其中的 ViewModel 是一种创新
    • ViewModel 是真正结合前端应用场景的实现
  • 如何理解MVVM
    • MVVM - Model View ViewModel,数据,视图,视图模型
    • 三者与 Vue 的对应:view 对应 templatevm 对应 new Vue({…})model 对应 data
    • 三者的关系:view 可以通过事件绑定的方式影响 modelmodel 可以通过数据绑定的形式影响到viewviewModel是把 model 和 view 连起来的连接器

如何实现 MVVM - 以 Vue.js 为例

MVVM 框架的三大要素

  • 响应式:Vue 如何监听到 data 的每个属性变化
  • 模板引擎:Vue 的模板如何被解析,指令如何处理
  • 渲染:Vue 的模板如何被渲染成 html,渲染过程是怎样的

Vue 如何实现响应式

  • 什么是响应式
    • 修改 data 属性之后,Vue 立刻监听到,立刻渲染页面
    • data 属性被代理到 vm 上
  • Object.defineProperty
    • 将对象属性的值的设置和访问 (get,set) 都变成函数,可以在当中加入我们自己的逻辑(进行监听)
    • 普通的 JavaScript 对象,做属性修改,我们监听不到,所以需要用到 Object.defineProperty
    • 既能get,又能set,才是双向数据绑定

Vue 如何解析模板

  • 模板是什么
    • 本质:模板就是字符串
    • 与html格式很像,但是模板中是有逻辑的,可以嵌入JS变量,如v-if, v-for等
    • 视图最终还是需要由模板生成 html 来显示
    • 模板必须先要转换成JS代码
      • 有逻辑(v-if, v-for),必须用JS才能实现(图灵完备)
      • 转换为html渲染页面,必须用JS才能实现
      • 因此,模板要转换成render函数
  • render函数
    • render函数包含了模板中所有的信息,返回 vnode,解决了模板中的逻辑(v-if, v-for)问题
    • 如何找到最终生成的render函数
      • 找到vue源码,搜索code.render,将code打印出来,就是生成的render函数
  • render函数与vdom
    • 模板生成 htmlvm._c
    • vm._c 和 snabbdom 中的 h 函数的实现很像,都是传入标签,属性,子元素作为参数
    • Vue.js 的 vdom 实现借鉴了 snabbdom
    • updateComponent 中实现了 vdom 的 patch
    • 页面首次渲染执行 updateComponent
    • data 中每次修改属性,都会执行 updateComponent

Vue.js 运行机制

Vue.js 是如何实现 MVVM 的?

  • 第一步:解析模板成 render 函数
    • 因为在打包的时候就已经生成了render函数,所以编译是第一步;响应式监听是在代码执行的时候才开始监听。
    • 模板中的所有信息都被render函数包含
    • 模板中用到的data中的属性,都变成了js变量
    • 模板中的 v-model v-for v-on都变成了js逻辑
    • render函数返回vnode
  • 第二步:响应式开始监听
    • 通过Object.definedProperty监听到对象属性的get和set
    • 将data的属性代理到vm上
  • 第三步:首次渲染,显示页面,且绑定依赖
    • 初次渲染,执行 updateComponent,执行 vm._render()
    • 执行 render 函数,会访问到 data 中的值,访问时会被响应式的 get 方法监听到
    • 执行 updateComponent,会走到 vdom 的 patch 方法
    • patch 将 vnode 渲染成 dom,初次渲染完成
    • 疑问:为何要监听 get,而不是直接监听 set ?
      • 因为 data 中有很多属性,有些被用到,有些可能不被用到
      • 只有被用到的才会走 get
      • 没有走到 get 中的属性,set 的时候我们也无需关心
      • 避免不必要的重新渲染
  • 第四步:data 属性变化,触发 re-render
    • 修改属性,被响应式的 set 监听到
    • set 中执行 updateComponent
    • updateComponent 重新执行 vm._render()
    • 生成的 vnode 和 prevVnode,通过 patch 进行对比
    • 渲染到 html 中