AI 读源码系列(一)- Vue 2响应式原理

0 阅读3分钟

Vue2 响应式原理解析

本文旨在通过源码视角,清晰地阐述 Vue 2.x 响应式原理的核心机制。Vue 的响应式本质上是一个自动化的观察者模式实现,它在数据变动与视图更新之间建立了一座无形的桥梁。


1. 核心模型:观察者模式

Vue 的响应式系统由三个核心角色协同工作:

角色源码类名形象比喻核心职责
劫持者Observer变压器递归遍历数据,利用 Object.defineProperty 将属性转化为 Getter/Setter。
发布者Dep通信兵每一个响应式属性唯一的依赖管理器。负责收集当前正在读取该属性的 Watcher,并在属性变化时发送通知。
订阅者Watcher执行者代表一个具体的任务(如组件渲染、计算属性求值、watch 回调)。当依赖的数据变化时,被动触发更新逻辑。

2. 响应式系统生命周期

阶段一:初始化数据劫持 (Initialization)

在 Vue 实例初始化时,Observer 会处理 data 中的每一个属性。

// src/core/observer/index.ts
export class Observer {
  constructor(public value: any) {
    this.dep = new Dep() // 为对象/数组本身也创建一个 Dep (用于 Vue.set)
    def(value, '__ob__', this) // 标记已观察
    
    if (Array.isArray(value)) {
      // 数组特殊处理:拦截变异方法
      augment(value, arrayMethods)
      this.observeArray(value)
    } else {
      // 对象处理:深度遍历属性
      this.walk(value)
    }
  }

  walk(obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}

阶段二:依赖收集 (Dependency Collection)

这是最巧妙的部分。当一个 Watcher(如渲染 Watcher)开始执行任务时,它会触发数据的 Getter。

原理图:依赖收集流程

flowchart TD
    subgraph &#34;订阅阶段 (Getter)&#34;
    A[&#34;Watcher 执行任务 (如渲染)&#34;] --> B[&#34;Push 到全局 Dep.target&#34;]
    B --> C[&#34;访问数据 (obj.key)&#34;]
    C --> D[&#34;触发 Getter&#34;]
    D --> E[&#34;调用 dep.depend()&#34;]
    E --> F[&#34;Watcher 与 Dep 互换信息&#34;]
    F --> G[&#34;Dep 将 Watcher 加入 subs 列表&#34;]
    G --> H[&#34;Pop 出 Dep.target&#34;]
    end
    
    style B fill:#f9f,stroke:#333
    style E fill:#bbf,stroke:#333
    style G fill:#dfd,stroke:#333

核心代码实现:

// src/core/observer/index.ts -> defineReactive
Object.defineProperty(obj, key, {
  get: function reactiveGetter() {
    // 关键点:Dep.target 存放着当前正在运行的 Watcher
    if (Dep.target) {
      dep.depend() // 收集依赖
      if (childOb) {
        childOb.dep.depend() // 处理嵌套对象
        if (Array.isArray(value)) dependArray(value) // 处理数组
      }
    }
    return value
  }
})

阶段三:派发更新 (Dispatch Update)

当数据被修改时,Setter 被触发,通知所有订阅了该属性的 Watcher。

原理图:派发更新流程

flowchart TD
    subgraph &#34;通知阶段 (Setter)&#34;
    A[&#34;修改数据 (obj.key = newVal)&#34;] --> B[&#34;触发 Setter&#34;]
    B --> C{&#34;数据是否真的变化?&#34;}
    C -- &#34;否&#34; --> D[&#34;退出&#34;]
    C -- &#34;是&#34; --> E[&#34;更新闭包内的 val&#34;]
    E --> F[&#34;对新值进行 observe(newVal)&#34;]
    F --> G[&#34;调用 dep.notify()&#34;]
    G --> H[&#34;遍历所有 subs (Watcher)&#34;]
    H --> I[&#34;调用 watcher.update()&#34;]
    I --> J[&#34;进入异步更新队列 (Scheduler)&#34;]
    end

    style G fill:#f96,stroke:#333
    style I fill:#bbf,stroke:#333

核心代码实现:

// src/core/observer/index.ts -> defineReactive
set: function reactiveSetter(newVal) {
  if (newVal === value) return
  value = newVal // 更新值
  childOb = observe(newVal) // 新值也需要变成响应式
  dep.notify() // 核心:通知订阅者
}

// src/core/observer/dep.ts
notify() {
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update() // 触发每一个订阅者的 update 方法
  }
}

3. 数组的特殊拦截

由于 Object.defineProperty 无法监听数组索引的变化,Vue 通过原型链覆盖的方式拦截了 7 个变异方法。

流程说明:

  1. 创建一个以 Array.prototype 为原型的拦截对象。
  2. 重写 push, pop, shift, unshift, splice, sort, reverse
  3. 在这些方法执行后,通过获取数组身上的 __ob__(即 Observer 实例),手动调用 ob.dep.notify()
// src/core/observer/array.ts 核心逻辑
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args) // 执行原方法
    const ob = this.__ob__
    let inserted
    // 针对新增元素,也需要进行响应式观察
    if (method === 'push' || method === 'unshift') inserted = args
    else if (method === 'splice') inserted = args.slice(2)
    if (inserted) ob.observeArray(inserted)
    
    ob.dep.notify() // 手动通知更新
    return result
  })
})

4. 总结:响应式系统的闭环

  1. 自动化:通过 Getter/Setter 实现了无需显式调用 setState 的数据绑定。
  2. 粒度精准:每个属性拥有自己的 Dep,只有依赖该属性的 Watcher 才会更新,避免了不必要的渲染。
  3. 性能优化Watcher 的更新并非同步执行,而是通过 queueWatcher 推入微任务队列,在同一事件循环中多次修改数据只会触发一次视图更新。