基于本文回答
0
评论

Vue 是如何实现 MVVM 的?

知识点图片

Vue 实现 MVVM(Model-View-ViewModel)的核心原理可以概括为:数据劫持(Data Hijacking)结合发布-订阅模式(Publisher-Subscriber Pattern)

简单来说,Vue 通过劫持数据对象的属性读写,在数据发生变化时通知订阅者,触发视图更新。

以下是详细的实现机制,主要包含三个核心模块:Observer(监听器)Compiler(编译器)Watcher(订阅者)


1. 核心模块解析

A. Observer(数据监听器)

它的作用是把一个普通的 JavaScript 对象转换成响应式对象

  • Vue 2.x 实现: 使用 Object.defineProperty()。Vue 会遍历 data 选项中的所有属性,为每个属性添加 gettersetter
    • Getter: 用于依赖收集。当视图读取某个属性时,Getter 被触发,Vue 记下“谁在这个时候用了我”。
    • Setter: 用于派发更新。当属性值被修改时,Setter 被触发,Vue 通知之前记下的所有依赖“我变了,你们该更新了”。
  • Vue 3.x 实现: 使用 ES6 的 Proxy。它可以直接代理整个对象,而不是遍历属性,性能更好且能监听数组索引变化和对象属性的添加/删除。

B. Compiler(指令解析器/编译器)

它的作用是解析模板指令,将模板中的变量替换成数据

  • 它扫描 DOM 节点,解析 {{}} 插值表达式和 v- 开头的指令(如 v-model, v-on)。
  • 将模板中的变量与数据关联起来。
  • 为每个包含动态数据的节点创建 Watcher,一旦数据变化,Watcher 就会收到通知并更新视图。

C. Watcher(订阅者/观察者)

它是 ObserverCompiler 之间的桥梁。

  • 连接点: 在 Compiler 解析模板时,会为每个数据绑定生成一个 Watcher。
  • 触发 Getter: Watcher 在初始化时会读取一次数据,从而触发 Observer 的 Getter,将自己添加到该数据的依赖容器(Dep)中。
  • 接收通知: 当数据变化触发 Setter 时,Dep 会通知 Watcher。
  • 执行更新: Watcher 收到通知后,调用自身的 update() 方法,触发回调函数(通常是 Virtual DOM 的 patch 过程)来更新视图。

D. Dep(依赖管理器)

这是一个专门用来管理 Watcher 的容器。

  • 每个响应式属性都有一个对应的 Dep 实例。
  • Dep 内部维护一个数组,用来存放所有依赖该属性的 Watcher。
  • 当数据变化时,Dep 负责遍历数组,通知所有 Watcher。

2. 整体工作流程

  1. 初始化(Init): Vue 实例启动,遍历 data 数据。
  2. 劫持(Observe): 利用 Object.defineProperty (Vue 2) 或 Proxy (Vue 3) 将数据转化为响应式,定义 Getter/Setter。
  3. 编译(Compile): 解析模板指令,将模板中的变量替换成数据,并初始化渲染页面。
  4. 依赖收集(Dependency Collection):
    • 在编译过程中,实例化 Watcher
    • Watcher 读取数据 -> 触发 Getter -> 将 Watcher 添加到该数据的 Dep 中。
  5. 派发更新(Notify):
    • 用户修改数据 -> 触发 Setter
    • Dep 收到信号,通知所有订阅的 Watcher
    • Watcher 调用 update 方法,更新视图(DOM)。

3. 双向绑定(v-model)是如何实现的?

MVVM 的显著特征是双向绑定。在 Vue 中,v-model 实际上是一个语法糖

它结合了两个操作:

  1. 数据绑定(Model -> View): 通过上述的 Observer/Watcher 机制,将数据绑定到 input 元素的 value 属性上。
  2. 事件监听(View -> Model): 在 input 元素上添加 input(或 change)事件监听器。当用户输入内容时,触发事件,在回调函数中将 input 的新值赋值给 data 中的对应属性。

代码示例(简化版):

html
<input v-model="message">
<!-- 等同于 -->
<input :value="message" @input="message = $event.target.value">

4. Vue 2 与 Vue 3 实现的区别

这是一个常见的面试追问点:

特性 Vue 2 (Object.defineProperty) Vue 3 (Proxy)
实现方式 递归遍历对象属性,逐个定义 getter/setter 代理整个对象,拦截对象的所有操作
对象新增属性 无法监听(需用 Vue.set 可以监听
对象删除属性 无法监听(需用 Vue.delete 可以监听
数组监听 无法监听索引和长度变化(重写了数组 7 个方法) 可以完美监听数组变化
性能 初始化时递归遍历,大对象性能较差 懒代理(访问时才代理),性能更好

总结

Vue 实现 MVVM 的核心在于:通过 Observer 劫持数据,通过 Compiler 解析模板,利用 DepWatcher 进行依赖收集和派发更新,从而实现数据驱动视图(Data Driven View)。而双向绑定则是通过在表单元素上自动添加事件监听来实现的。

右滑查看面试常问